Istnieją ograniczenia dotyczące arytmetycznych możliwości oceny bash
powłoki. Podręcznik zwięźle opisuje ten aspekt arytmetyki powłoki, ale stwierdza :
Oceny dokonuje się w liczbach całkowitych o stałej szerokości bez sprawdzania przepełnienia, chociaż dzielenie przez 0 jest zatrzymywane i oznaczane jako błąd. Operatory i ich pierwszeństwo, asocjatywność i wartości są takie same jak w języku C.
Która liczba całkowita o stałej szerokości, do której się odnosi, tak naprawdę dotyczy tego, który typ danych jest używany (i szczegóły, dlaczego jest to poza tym), ale wartość graniczna jest wyrażana /usr/include/limits.h
w następujący sposób:
# if __WORDSIZE == 64
# define ULONG_MAX 18446744073709551615UL
# ifdef __USE_ISOC99
# define LLONG_MAX 9223372036854775807LL
# define ULLONG_MAX 18446744073709551615ULL
A kiedy już to wiesz, możesz potwierdzić ten stan faktyczny w następujący sposób:
# getconf -a | grep 'long'
LONG_BIT 64
ULONG_MAX 18446744073709551615
Jest to liczba całkowita 64-bitowa, co przekłada się bezpośrednio na powłokę w kontekście obliczeń arytmetycznych:
# echo $(((2**63)-1)); echo $((2**63)); echo $(((2**63)+1)); echo $((2**64))
9223372036854775807 //the practical usable limit for your everyday use
-9223372036854775808 //you're that much "away" from 2^64
-9223372036854775807
0
# echo $((9223372036854775808+9223372036854775807))
-1
Tak więc między 2 63 a 2 64 -1 otrzymasz ujemne liczby całkowite pokazujące, jak daleko od ULONG_MAX jesteś 1 . Gdy ocena osiąga ten limit i przepełnia się, w jakiejkolwiek kolejności, nie pojawia się żadne ostrzeżenie, a ta część oceny jest resetowana do zera, co może dawać pewne nietypowe zachowanie z czymś takim, jak na przykład potęgowanie prawostronne :
echo $((6**6**6)) 0 // 6^46656 overflows to 0
echo $((6**6**6**6)) 1 // 6^(6^46656) = 6^0 = 1
echo $((6**6**6**6**6)) 6 // 6^(6(6^46656)) = 6^(6^0) = 6^1
echo $((6**6**6**6**6**6)) 46656 // 6^(6^(6^(6^46656))) = 6^6
echo $((6**6**6**6**6**6**6)) 0 // = 6^6^6^1 = 0
...
Używanie sh -c 'command'
niczego nie zmienia, więc muszę założyć, że jest to normalne i zgodne wyjście. Teraz, gdy myślę, że mam podstawową, ale konkretną wiedzę na temat zakresu arytmetycznego i limitu oraz co to znaczy w powłoce do oceny wyrażeń, pomyślałem, że mogę szybko zerknąć, jakie typy danych używają inne oprogramowanie w systemie Linux. Użyłem niektórych bash
źródeł, które musiałem uzupełnić do wprowadzenia tego polecenia:
{ shopt -s globstar; for i in /path/to/source_bash-4.2/include/**/*.h /usr/include/**/*.h; do grep -HE '\b(([UL])|(UL)|())LONG|\bFLOAT|\bDOUBLE|\bINT' $i; done; } | grep -iE 'bash.*max'
bash-4.2/include/typemax.h:# define LLONG_MAX TYPE_MAXIMUM(long long int)
bash-4.2/include/typemax.h:# define ULLONG_MAX TYPE_MAXIMUM(unsigned long long int)
bash-4.2/include/typemax.h:# define INT_MAX TYPE_MAXIMUM(int)
if
Instrukcje mają więcej danych wyjściowych i mogę wyszukiwać polecenia takie jak awk
itp. Zauważyłem, że użyte wyrażenie regularne nie łapie niczego na temat narzędzi o dowolnej precyzji, takich jak bc
i dc
.
pytania
- Jaki jest powód, aby nie ostrzegać cię (podobnie jak w
awk
przypadku oceny 2 ^ 1024), gdy twoja arytmetyczna przepełnia się? Dlaczego ujemne liczby całkowite między 2 63 a 2 64 -1 są narażone na końcowy użytkownik, gdy coś ocenia? - Czytałem gdzieś, że jakiś smak UNIXa może interaktywnie zmienić ULONG_MAX? Czy ktoś o tym słyszał?
- Jeśli ktoś dowolnie zmieni wartość maksimum liczby całkowitej bez znaku w
limits.h
, a następnie przekompilujebash
, czego możemy się spodziewać?
Uwaga
1. Chciałem lepiej zilustrować to, co zobaczyłem, ponieważ jest to bardzo prosta sprawa empiryczna. Zauważyłem, że:
- (a) Każda ocena, która daje <2 ^ 63-1, jest poprawna
- (b) Każda ocena, która daje => 2 ^ 63 do 2 ^ 64 daje ujemną liczbę całkowitą:
- Zakres tej liczby całkowitej wynosi od x do y. x = -9223372036854775808 iy = 0.
Biorąc to pod uwagę, ocenę podobną do (b) można wyrazić jako 2 ^ 63-1 plus coś w obrębie x..y. Na przykład, jeśli jesteśmy dosłownie poproszeni o ocenę (2 ^ 63-1) +100 002 (ale może być dowolną liczbą mniejszą niż w (a)), otrzymujemy -9223372036854675807. Podaję tylko oczywiste, ale chyba oznacza to, że dwa następujące wyrażenia:
- (2 ^ 63-1) + 100 002 ORAZ;
- (2 ^ 63-1) + (LLONG_MAX - {za co daje nam powłoka ((2 ^ 63-1) + 100 002), czyli -9223372036854675807}) cóż, używając dodatnich wartości, które mamy;
- (2 ^ 63-1) + (9223372036854775807 - 9223372036854675807 = 100 000)
- = 9223372036854775807 + 100 000
są naprawdę bardzo blisko. Drugie wyrażenie to „2” oprócz (2 ^ 63-1) + 100 002, czyli tego, co oceniamy. Mam na myśli to, że otrzymujesz ujemne liczby całkowite pokazujące, jak daleko jesteś od 2 ^ 64. Mam na myśli te ujemne liczby całkowite i znajomość granic, no cóż, nie można zakończyć oceny w zakresie x..y w powłoce bash, ale można to zrobić gdzie indziej - dane są użyteczne do 2 ^ 64 w tym sensie (mógłbym dodać na papierze lub użyj go w bc). Poza tym jednak zachowanie jest podobne do 6 ^ 6 ^ 6, ponieważ limit został osiągnięty, jak opisano poniżej w Q ...
bc
np $num=$(echo 6^6^6 | bc)
. : Niestety bc
wstawia podział wiersza, więc musisz num=$(echo $num | sed 's/\\\s//g')
później; jeśli zrobisz to w potoku, istnieją rzeczywiste znaki nowej linii, które są niezręczne w przypadku sed, chociaż num=$(echo 6^6^3 | bc | perl -pne 's/\\\s//g')
działa. W obu przypadkach masz teraz liczbę całkowitą, której można użyć, np num2=$(echo "$num * 2" | bc)
.
bc
przez ustawienie BC_LINE_LENGTH=0
.