Esistono limiti per le capacità di valutazione aritmetica della bash
shell. Il manuale è sintetico su questo aspetto dell'aritmetica della shell ma afferma :
La valutazione viene eseguita in numeri interi a larghezza fissa senza controllo di overflow, sebbene la divisione per 0 sia intrappolata e contrassegnata come errore. Gli operatori e la loro precedenza, associatività e valori sono gli stessi del linguaggio C.
A quale numero intero a larghezza fissa si riferisce in realtà riguarda quale tipo di dati viene utilizzato (e le specifiche del motivo per cui questo è oltre questo) ma il valore limite è espresso /usr/include/limits.h
in questo modo:
# if __WORDSIZE == 64
# define ULONG_MAX 18446744073709551615UL
# ifdef __USE_ISOC99
# define LLONG_MAX 9223372036854775807LL
# define ULLONG_MAX 18446744073709551615ULL
E una volta che lo sai, puoi confermare questo stato di fatto in questo modo:
# getconf -a | grep 'long'
LONG_BIT 64
ULONG_MAX 18446744073709551615
Questo è un numero intero a 64 bit e questo si traduce direttamente nella shell nel contesto della valutazione aritmetica:
# 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
Quindi tra 2 63 e 2 64 -1, ottieni numeri negativi che mostrano quanto lontano da ULONG_MAX sei 1 . Quando la valutazione raggiunge quel limite e trabocca, per qualunque ordine, non si riceve alcun avviso e quella parte della valutazione viene reimpostata su 0, il che può comportare un comportamento insolito con qualcosa come l' espiazione associativa giusta per esempio:
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
...
L'uso sh -c 'command'
non cambia nulla, quindi devo presumere che sia un output normale e conforme. Ora che penso di avere una comprensione di base ma concreta della gamma e del limite aritmetici e di cosa significhi nella shell per la valutazione delle espressioni, ho pensato di poter dare rapidamente un'occhiata a quali tipi di dati utilizzano gli altri software in Linux. Ho usato alcune bash
fonti che dovevo integrare l'input di questo comando:
{ 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)
C'è più output con le if
istruzioni e posso cercare un comando come awk
anche ecc. Vedo che l'espressione regolare che ho usato non prende nulla sugli strumenti di precisione arbitrari che ho come bc
e dc
.
Domande
- Qual è la logica per non avvisarti (come
awk
accade quando si valuta 2 ^ 1024) quando la valutazione aritmetica trabocca? Perché gli interi negativi tra 2 63 e 2 64 -1 sono esposti all'utente finale quando sta valutando qualcosa? - Ho letto da qualche parte che un po 'di sapore di UNIX può cambiare interattivamente ULONG_MAX? Qualcuno ne ha sentito parlare?
- Se qualcuno modifica arbitrariamente il valore dell'intero senza segno massimo in
limits.h
, quindi ricompilabash
, cosa possiamo aspettarci che accada?
Nota
1. Volevo illustrare più chiaramente ciò che ho visto, in quanto è roba empirica molto semplice. Quello che ho notato è che:
- (a) Qualsiasi valutazione che dia <2 ^ 63-1 è corretta
- (b) Qualsiasi valutazione che dia => 2 ^ 63 fino a 2 ^ 64 fornisce un numero intero negativo:
- L'intervallo di tale numero intero è da x a y. x = -9223372036854775808 e y = 0.
Considerando questo, una valutazione che è come (b) può essere espressa come 2 ^ 63-1 più qualcosa all'interno di x..y. Ad esempio, se ci viene chiesto letteralmente di valutare (2 ^ 63-1) +100 002 (ma potrebbe essere qualsiasi numero più piccolo di in (a)) otteniamo -9223372036854675807. Sto solo affermando l'ovvio immagino, ma questo significa anche che le due seguenti espressioni:
- (2 ^ 63-1) + 100 002 AND;
- (2 ^ 63-1) + (LLONG_MAX - {cosa ci dà la shell ((2 ^ 63-1) + 100 002), che è -9223372036854675807}), usando valori positivi che abbiamo;
- (2 ^ 63-1) + (9223372036854775807 - 9223372036854675807 = 100 000)
- = 9223372036854775807 + 100000
sono davvero molto vicini. La seconda espressione è "2" a parte (2 ^ 63-1) + 100 002 ovvero ciò che stiamo valutando. Questo è ciò che intendo per ottenere numeri negativi che mostrano quanto sei lontano da 2 ^ 64. Voglio dire con quegli interi negativi e la conoscenza dei limiti, beh non puoi finire la valutazione all'interno dell'intervallo x..y nella shell bash ma puoi farlo altrove - i dati sono utilizzabili fino a 2 ^ 64 in quel senso (potrei aggiungere su su carta o usalo in bc). Oltre a ciò, tuttavia, il comportamento è simile a quello di 6 ^ 6 ^ 6 poiché il limite viene raggiunto come descritto di seguito in Q ...
bc
, come ad esempio: $num=$(echo 6^6^6 | bc)
. Sfortunatamente, bc
mette in pausa le interruzioni, quindi è necessario in num=$(echo $num | sed 's/\\\s//g')
seguito; se lo fai in una pipa, ci sono dei veri personaggi newline, che sono scomodi con sed, anche se num=$(echo 6^6^3 | bc | perl -pne 's/\\\s//g')
funziona. In entrambi i casi ora hai un numero intero che può essere usato, ad es num2=$(echo "$num * 2" | bc)
.
bc
impostando BC_LINE_LENGTH=0
.