Qual è la logica della shell bash che non ti avverte di overflow aritmetico ecc.?


9

Esistono limiti per le capacità di valutazione aritmetica della bashshell. 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.hin 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 bashfonti 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 ifistruzioni e posso cercare un comando come awkanche ecc. Vedo che l'espressione regolare che ho usato non prende nulla sugli strumenti di precisione arbitrari che ho come bce dc.


Domande

  1. Qual è la logica per non avvisarti (come awkaccade 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?
  2. Ho letto da qualche parte che un po 'di sapore di UNIX può cambiare interattivamente ULONG_MAX? Qualcuno ne ha sentito parlare?
  3. Se qualcuno modifica arbitrariamente il valore dell'intero senza segno massimo in limits.h, quindi ricompila bash, 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 ...


5
La mia ipotesi è che la logica si riduce a "il guscio non è lo strumento giusto per la matematica". Non è progettato per questo e non tenta di gestirlo con grazia come mostri. Diavolo, la maggior parte delle conchiglie non ha nemmeno a che fare con i galleggianti!
terdon

@terdon Anche se in questo caso la shell gestisce i numeri è esattamente la stessa di ogni linguaggio di alto livello di cui abbia mai sentito parlare. I tipi interi hanno dimensioni fisse e possono traboccare.
Riccioli d'oro,

@terdon In effetti, mentre studiavo questo dal momento in cui il tempo 6 ^ 6 ^ 6 Q mi sono reso conto che. Ho anche indovinato il motivo per cui non sono riuscito a trovare molti contenuti perché questo aveva a che fare con C, o anche con C99. Dato che non sono né uno sviluppatore né una persona IT, devo fare i conti con tutte le conoscenze che fanno da sfondo a questi presupposti. Sicuramente qualcuno che richiede una precisione arbitraria conosce il tipo di dati ma ovviamente non sono quella persona :) (ma ho notato il comportamento di awk @ 2 ^ 53 + 1 cioè float double; è solo precisione e interni vs. stampa ecc. È al di là di me !).

1
Se si desidera lavorare con grandi numeri nella shell, uso bc, come ad esempio: $num=$(echo 6^6^6 | bc). Sfortunatamente, bcmette 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).
Riccioli d'oro

1
... Qualcuno qui ha sottolineato che è possibile disabilitare questa funzione di interruzione di riga bcimpostando BC_LINE_LENGTH=0.
Riccioli d'oro,

Risposte:


11

Quindi tra 2 ^ 63 e 2 ^ 64-1, ricevi numeri interi negativi che ti mostrano quanto sei lontano da ULONG_MAX.

No. Come lo pensi? Con il tuo esempio, il massimo è:

> max=$((2**63 - 1)); echo $max
9223372036854775807

Se "overflow" significa "ottieni numeri negativi che mostrano quanto sei lontano da ULONG_MAX", quindi se ne aggiungiamo uno, non dovremmo ottenere -1? Ma invece:

> echo $(($max + 1))
-9223372036854775808

Forse vuoi dire che questo è un numero che puoi aggiungere $maxper ottenere una differenza negativa, poiché:

> echo $(($max + 1 + $max))
-1

Ma questo in realtà non continua a essere vero:

> echo $(($max + 2 + $max))
0

Questo perché il sistema utilizza il complemento a due per implementare numeri interi con segno. 1 Il valore risultante da un overflow NON è un tentativo di fornire una differenza, una differenza negativa, ecc. È letteralmente il risultato del troncamento di un valore in un numero limitato di bit, quindi interpretandolo come un intero con segno del complemento a due . Ad esempio, il motivo $(($max + 1 + $max))viene fuori come -1 perché il valore più alto nel complemento a due è tutti i bit impostati tranne il bit più alto (che indica negativo); sommarli insieme significa sostanzialmente portare tutti i bit a sinistra, quindi si finisce con (se le dimensioni erano 16 bit e non 64):

11111111 11111110

Il bit alto (segno) è ora impostato perché riportato nell'aggiunta. Se ne aggiungi un altro (00000000 00000001), hai impostato tutti i bit , che nel complemento a due è -1.

Penso che risponda parzialmente alla seconda metà della prima domanda: "Perché gli interi negativi ... sono esposti all'utente finale?". Primo, perché questo è il valore corretto secondo le regole dei numeri di complemento a due bit a 64 bit. Questa è la pratica convenzionale della maggior parte dei (altri) linguaggi di programmazione di alto livello (non riesco a pensare a uno che non lo fa), quindi bashaderire alla convenzione. Quale è anche la risposta alla prima parte della prima domanda: "Qual è la logica?": Questa è la norma nella specifica dei linguaggi di programmazione.

WRT la seconda domanda, non ho sentito parlare di sistemi che cambiano interattivamente ULONG_MAX.

Se qualcuno modifica arbitrariamente il valore del numero intero senza segno in limits.h, quindi ricompila bash, cosa possiamo aspettarci che accada?

Non farebbe alcuna differenza nel modo in cui l'aritmetica viene fuori, perché questo non è un valore arbitrario che viene utilizzato per configurare il sistema - è un valore di convenienza che memorizza una costante immutabile che riflette l'hardware. Per analogia, è possibile ridefinire c in 55 mph, ma la velocità della luce sarà comunque 186.000 miglia al secondo. c non è un numero usato per configurare l'universo - è una deduzione sulla natura dell'universo.

ULONG_MAX è esattamente lo stesso. Viene dedotto / calcolato in base alla natura dei numeri N-bit. Cambiarlo limits.hsarebbe una pessima idea se quella costante fosse usata da qualche parte supponendo che dovesse rappresentare la realtà del sistema .

E non puoi cambiare la realtà imposta dal tuo hardware.


1. Non penso che questo (il mezzo di rappresentazione dei numeri interi) sia effettivamente garantito da bash, poiché dipende dalla libreria C sottostante e lo standard C non lo garantisce. Tuttavia, questo è ciò che viene utilizzato sulla maggior parte dei normali computer moderni.


Sono molto grato! Venire a patti con l'elefante nella stanza e pensare. Sì, nella prima parte si tratta principalmente di parole. Ho aggiornato la mia Q per mostrare cosa intendevo dire. Cercherò perché il complemento di due descrive parte di ciò che ho visto e la tua risposta è preziosa per capirlo! Per quanto riguarda UNIX Q, devo aver letto male qualcosa su ARG_MAX con AIX qui . Saluti!

1
In effetti puoi usare il complemento a due per determinare il valore se sei sicuro di essere nell'intervallo> 2 * $max, come descrivi. I miei punti sono 1) non è questo lo scopo, 2) assicurati di capire se vuoi farlo, 3) non è molto utile a causa dell'applicabilità molto limitata, 4) secondo la nota non è effettivamente garantito che il sistema lo faccia usa il complemento a due. In breve, tentare di sfruttare quel codice del programma sarebbe considerato una pratica molto scadente. Esistono librerie / moduli "big number" (per shell in POSIX, bc) - usa quelli se necessario.
Riccioli d'oro,

È solo di recente che ho visto qualcosa che ha sfruttato il complemento a due per implementare un ALU con un sommatore binario a 4 bit con IC carry veloce; c'era persino un confronto con il proprio complemento (per vedere come fosse). La tua spiegazione è stata determinante nel fatto che sono stato in grado di nominare e collegare ciò che ho visto qui con ciò che è stato discusso in quei video , aumentando le possibilità che potrei davvero cogliere tutte le implicazioni lungo la linea una volta che tutto affonda. Grazie ancora! Saluti!
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.