Was ist der Grund dafür, dass die Bash-Shell Sie nicht vor arithmetischem Überlauf usw. warnt?

9

Für die arithmetischen Auswertungsfunktionen der bashShell sind Grenzen festgelegt . Das Handbuch ist kurz und bündig zu diesem Aspekt der Shell-Arithmetik, lautet jedoch :

Die Auswertung erfolgt in Ganzzahlen mit fester Breite ohne Überprüfung auf Überlauf, obwohl die Division durch 0 abgefangen und als Fehler gekennzeichnet wird. Die Operatoren und ihre Priorität, Assoziativität und Werte sind dieselben wie in der C-Sprache.

Auf welche Ganzzahl mit fester Breite sich dies bezieht, hängt wirklich davon ab, welcher Datentyp verwendet wird (und die Einzelheiten dazu liegen darüber hinaus), aber der Grenzwert wird folgendermaßen ausgedrückt /usr/include/limits.h:

#  if __WORDSIZE == 64
#   define ULONG_MAX     18446744073709551615UL
#  ifdef __USE_ISOC99
#  define LLONG_MAX       9223372036854775807LL
#  define ULLONG_MAX    18446744073709551615ULL

Und sobald Sie das wissen, können Sie diesen Sachverhalt folgendermaßen bestätigen:

# getconf -a | grep 'long'
LONG_BIT                           64
ULONG_MAX                          18446744073709551615

Dies ist eine 64-Bit-Ganzzahl, die im Kontext der arithmetischen Auswertung direkt in die Shell übersetzt wird:

# 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

So zwischen 2 63 und 2 64 -1, erhalten Sie negative ganze Zahlen Sie zeigen , wie weit weg von ULONG_MAX Sie sind 1 . Wenn die Auswertung erreicht diese Grenze und überläuft, durch was auch immer , um das heißt, Sie keine Warnung erhalten und dass ein Teil der Auswertung auf 0 zurückgesetzt, die mit so etwas wie etwas ungewöhnlichen Verhalten ergeben können rechtsassoziativ Potenzierung zum Beispiel:

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
...

Die Verwendung sh -c 'command'ändert nichts, daher muss ich davon ausgehen, dass dies eine normale und konforme Ausgabe ist. Jetzt, da ich denke, dass ich ein grundlegendes, aber konkretes Verständnis des arithmetischen Bereichs und der Grenze habe und was dies in der Shell für die Auswertung von Ausdrücken bedeutet, dachte ich, ich könnte schnell einen Blick darauf werfen, welche Datentypen die andere Software unter Linux verwendet. Ich habe einige bashQuellen verwendet, die ich zur Eingabe dieses Befehls ergänzen musste:

{ 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)

Es gibt mehr Leistung mit den ifAussagen und ich kann wie für einen Befehl suchen mit awkdem regulären Ausdruck I verfängt nichts über willkürliches Präzisionswerkzeug Ich habe wie verwendet zu etc. Ich Mitteilung bcund dc.


Fragen

  1. Was ist der Grund, warum Sie nicht gewarnt werden (wie awkbei der Bewertung von 2 ^ 1024), wenn Ihre arithmetische Bewertung überläuft? Warum sind die negativen Ganzzahlen zwischen 2 63 und 2 64 -1 dem Endbenutzer ausgesetzt, wenn er etwas bewertet?
  2. Ich habe irgendwo gelesen, dass eine UNIX-Variante ULONG_MAX interaktiv ändern kann. Hat jemand davon gehört?
  3. Wenn jemand den Wert des vorzeichenlosen Ganzzahlmaximums willkürlich ändert limits.hund dann neu kompiliert bash, was können wir dann erwarten?

Hinweis

1. Ich wollte klarer veranschaulichen, was ich gesehen habe, da es sich um sehr einfache empirische Dinge handelt. Was mir aufgefallen ist, ist Folgendes:

  • (a) Jede Bewertung, die <2 ^ 63-1 ergibt, ist korrekt
  • (b) Jede Bewertung, die => 2 ^ 63 bis 2 ^ 64 ergibt, ergibt eine negative ganze Zahl:
    • Der Bereich dieser Ganzzahl liegt zwischen x und y. x = -9223372036854775808 und y = 0.

In Anbetracht dessen kann eine Bewertung, die wie (b) ist, als 2 ^ 63-1 plus etwas innerhalb von x..y ausgedrückt werden. Wenn wir zum Beispiel buchstäblich gebeten werden, (2 ^ 63-1) +100 002 zu bewerten (aber eine beliebige Zahl kleiner als in (a) sein kann), erhalten wir -9223372036854675807. Ich sage nur das Offensichtliche, aber das bedeutet auch, dass die beiden folgenden Ausdrücke:

  • (2 ^ 63-1) + 100 002 AND;
  • (2 ^ 63-1) + (LLONG_MAX - {was die Shell uns für ((2 ^ 63-1) + 100 002) gibt, das ist -9223372036854675807}) gut, unter Verwendung positiver Werte, die wir haben;
    • (2 ^ 63-1) + (9223372036854775807 - 9223372036854675807 = 100 000)
    • = 9223372036854775807 + 100 000

sind in der Tat sehr nah. Der zweite Ausdruck ist "2", abgesehen von (2 ^ 63-1) + 100 002, dh was wir bewerten. Das meine ich damit, dass Sie negative ganze Zahlen erhalten, die Ihnen zeigen, wie weit Sie von 2 ^ 64 entfernt sind. Ich meine, mit diesen negativen ganzen Zahlen und der Kenntnis der Grenzen können Sie die Auswertung nicht innerhalb des x..y-Bereichs in der Bash-Shell beenden, aber Sie können anderswo - die Daten können in diesem Sinne bis zu 2 ^ 64 verwendet werden (könnte ich hinzufügen) es auf Papier oder verwenden Sie es in bc). Darüber hinaus ist das Verhalten jedoch dem von 6 ^ 6 ^ 6 ähnlich, da die Grenze erreicht wird, wie nachstehend in Q ...


quelle
5
Ich vermute, dass die Begründung auf "die Shell ist nicht das richtige Werkzeug für die Mathematik" hinausläuft. Es ist nicht dafür konzipiert und versucht nicht, wie Sie zeigen, anmutig damit umzugehen. Verdammt, die meisten Muscheln haben nicht einmal mit Schwimmern zu tun!
Terdon
@terdon Obwohl die Art und Weise, wie die Shell mit Zahlen umgeht, in diesem Fall genau die gleiche ist wie in jeder Hochsprache, von der ich je gehört habe. Ganzzahlige Typen haben eine feste Größe und können überlaufen.
Goldlöckchen
@terdon In der Tat, als ich dies seit dem 6 ^ 6 ^ 6 Timing recherchierte, wurde QI dies klar. Ich vermutete auch, dass ich nicht viel Inhalt finden konnte, weil dies mit C oder sogar C99 zu tun hatte. Da ich weder Entwickler noch IT-Mitarbeiter bin, muss ich mich mit all dem Wissen auseinandersetzen, das diesen Annahmen zugrunde liegt. Sicherlich kennt jemand, der willkürliche Präzision benötigt, den Datentyp, aber offensichtlich bin ich nicht diese Person :) (aber ich habe das Verhalten von awk bei 2 ^ 53 + 1 bemerkt, dh float double; nur Präzision und intern vs. Drucken usw. sind mir ein Rätsel !).
1
Wenn Sie mit großen Zahlen in der Shell arbeiten möchten, verwenden Sie bcz $num=$(echo 6^6^6 | bc). bcSetzt leider Zeilenumbrüche ein, so dass Sie num=$(echo $num | sed 's/\\\s//g')danach; Wenn Sie es in einer Pipe tun, gibt es tatsächlich Zeilenumbrüche, die mit sed umständlich sind, obwohl sie num=$(echo 6^6^3 | bc | perl -pne 's/\\\s//g')funktionieren. In beiden Fällen haben Sie jetzt eine Ganzzahl, die verwendet werden kann, z num2=$(echo "$num * 2" | bc).
Goldlöckchen
1
... Jemand hier hat darauf hingewiesen, dass Sie diese Zeilenumbruchfunktion bcdurch Einstellen deaktivieren können BC_LINE_LENGTH=0.
Goldlöckchen

Antworten:

11

Zwischen 2 ^ 63 und 2 ^ 64-1 erhalten Sie also negative Ganzzahlen, die anzeigen, wie weit Sie von ULONG_MAX entfernt sind.

Nein . Wie kommst du darauf? Nach Ihrem eigenen Beispiel ist das Maximum:

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

Wenn "Überlauf" bedeutet "Sie erhalten negative Ganzzahlen, die Ihnen zeigen, wie weit Sie von ULONG_MAX entfernt sind", sollten wir dann nicht -1 erhalten, wenn wir eine hinzufügen? Aber stattdessen:

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

Vielleicht meinen Sie, dass dies eine Zahl ist, die Sie hinzufügen können $max, um einen negativen Unterschied zu erhalten, da:

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

Dies gilt jedoch nicht weiter:

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

Dies liegt daran, dass das System das Zweierkomplement verwendet , um vorzeichenbehaftete Ganzzahlen zu implementieren. 1 Der Wert, der sich aus einem Überlauf ergibt, ist KEIN Versuch, Ihnen eine Differenz, eine negative Differenz usw. zu liefern. Er ist buchstäblich das Ergebnis des Abschneidens eines Werts auf eine begrenzte Anzahl von Bits und der anschließenden Interpretation als Ganzzahl mit Zweierkomplement . Der Grund $(($max + 1 + $max))für -1 ist beispielsweise, dass der höchste Wert im Zweierkomplement alle gesetzten Bits außer dem höchsten Bit (was negativ anzeigt) sind. Wenn Sie diese addieren, müssen Sie im Grunde alle Bits nach links tragen, sodass Sie am Ende (wenn die Größe 16 Bit und nicht 64 Bit beträgt) Folgendes haben:

11111111 11111110

Das High-Bit (Vorzeichen) wird jetzt gesetzt, da es in der Addition übertragen wurde. Wenn Sie noch eine (00000000 00000001) hinzufügen, sind alle Bits gesetzt , was im Zweierkomplement -1 ist.

Ich denke, das beantwortet teilweise die zweite Hälfte Ihrer ersten Frage - "Warum sind die negativen ganzen Zahlen ... dem Endbenutzer ausgesetzt?". Erstens, weil dies der richtige Wert gemäß den Regeln der 64-Bit-Zweierkomplementzahlen ist. Dies ist die konventionelle Praxis der meisten (anderen) Allzweck-Programmiersprachen auf hoher Ebene (ich kann mir keine vorstellen, die dies nicht tut), daher bashwird die Konvention eingehalten. Welches ist auch die Antwort auf den ersten Teil der ersten Frage - "Was ist die Begründung?": Dies ist die Norm in der Spezifikation von Programmiersprachen.

WRT die 2. Frage, ich habe noch nichts von Systemen gehört, die ULONG_MAX interaktiv verändern.

Wenn jemand den Wert des vorzeichenlosen Ganzzahlmaximums in limit.h willkürlich ändert und dann bash neu kompiliert, was können wir dann erwarten?

Es würde keinen Unterschied machen, wie die Arithmetik herauskommt, da dies kein willkürlicher Wert ist, der zum Konfigurieren des Systems verwendet wird - es ist ein Komfortwert, der eine unveränderliche Konstante speichert, die die Hardware widerspiegelt. In Analogie könnten Sie c auf 55 Meilen pro Stunde neu definieren , aber die Lichtgeschwindigkeit beträgt immer noch 186.000 Meilen pro Sekunde. c ist keine Zahl, die zur Konfiguration des Universums verwendet wird - es ist ein Abzug über die Natur des Universums.

ULONG_MAX ist genau das gleiche. Sie wird basierend auf der Art der N-Bit-Zahlen abgeleitet / berechnet. Eine Änderung limits.hwäre eine sehr schlechte Idee, wenn diese Konstante irgendwo verwendet wird, vorausgesetzt, sie soll die Realität des Systems darstellen .

Und Sie können die von Ihrer Hardware auferlegte Realität nicht ändern.


1. Ich denke nicht, dass dies (das Mittel der Ganzzahldarstellung) tatsächlich durch garantiert wird bash, da es von der zugrunde liegenden C-Bibliothek abhängt und Standard C dies nicht garantiert. Dies wird jedoch auf den meisten normalen modernen Computern verwendet.

Goldlöckchen
quelle
Ich bin sehr dankbar! Sich mit dem Elefanten im Raum abfinden und nachdenken. Ja, im ersten Teil geht es hauptsächlich um Worte. Ich habe mein Q aktualisiert, um zu zeigen, was ich gemeint habe. Ich werde untersuchen, warum die Ergänzung zu zwei etwas von dem beschreibt, was ich gesehen habe, und Ihre Antwort ist von unschätzbarem Wert, um das zu verstehen! Was UNIX Q betrifft, muss ich hier etwas über ARG_MAX mit AIX falsch verstanden haben . Prost!
1
Tatsächlich können Sie das Zweierkomplement verwenden, um den Wert zu bestimmen, wenn Sie sicher sind, dass Sie sich $maxwie beschrieben im Bereich> 2 * befinden . Meine Punkte sind 1) das ist nicht der Zweck, 2) stellen Sie sicher, dass Sie verstehen, wenn Sie das tun möchten, 3) es ist nicht sehr nützlich wegen der sehr eingeschränkten Anwendbarkeit, 4) gemäß der Fußnote ist es nicht wirklich garantiert, dass das System dies tut Verwenden Sie das Zweierkomplement. Kurz gesagt, der Versuch, dies im Programmcode auszunutzen, wäre eine sehr schlechte Praxis. Es gibt "große Anzahl" Bibliotheken / Module (für Shells unter POSIX bc) - verwenden Sie diese, wenn nötig.
Goldlöckchen
Erst kürzlich habe ich etwas gesehen, das das Komplement der beiden nutzte, um eine ALU mit einem 4-Bit-Binäraddierer mit schnellem Übertrags-IC zu implementieren. Es gab sogar einen Vergleich mit der eigenen Ergänzung (um zu sehen, wie schlecht es war). Ihre Erklärung hat maßgeblich dazu beigetragen, dass ich das, was ich hier gesehen habe, mit dem, was in diesen Videos besprochen wurde, benennen und verbinden konnte, was die Wahrscheinlichkeit erhöht, dass ich wirklich alle Implikationen auf der ganzen Linie erfassen kann, sobald alles einsinkt. Nochmals vielen Dank dafür! Prost!