Wie geht Java mit ganzzahligen Unter- und Überläufen um?
Wie würden Sie vor diesem Hintergrund überprüfen / testen, ob dies geschieht?
java
integer
integer-overflow
KushalP
quelle
quelle
checked
. Ich sehe nicht, dass es viel verwendet wird, und das Tippenchecked { code; }
ist ungefähr so viel Arbeit wie das Aufrufen einer Methode.csc /checked ...
oder legen Sie die Eigenschaft im Eigenschaftenbereich des Projekts in Visual Studio fest.Antworten:
Wenn es überläuft, kehrt es zum Mindestwert zurück und fährt von dort fort. Wenn es unterläuft, kehrt es zum Maximalwert zurück und fährt von dort fort.
Sie können dies vorab wie folgt überprüfen:
(Sie können ersetzen
int
durchlong
die gleichen Prüfungen für auszuführenlong
)Wenn Sie der Meinung sind, dass dies häufiger vorkommt, sollten Sie einen Datentyp oder ein Objekt verwenden, in dem größere Werte gespeichert werden können, z . B.
long
oder möglicherweisejava.math.BigInteger
. Der letzte läuft praktisch nicht über, der verfügbare JVM-Speicher ist die Grenze.Wenn Sie sich bereits in Java8 befinden, können Sie die neuen Methoden
Math#addExact()
undMath#subtractExact()
Methoden verwenden, die einenArithmeticException
Überlauf auslösen .Den Quellcode finden Sie hier bzw. hier .
Natürlich können Sie sie auch sofort verwenden, anstatt sie in einer
boolean
Dienstprogrammmethode zu verstecken .quelle
+100, -100
, sind. Wenn Sie einer Java-Ganzzahl eine hinzufügen, sieht der Prozess beim Überlaufen folgendermaßen aus.98, 99, 100, -100, -99, -98, ...
. Ist das sinnvoller?Math#addExact
ist die Syntax, die normalerweise beim Schreiben von Javadocs verwendet wird - während normalerweise diese konvertiert wirdMath.addExact
, bleibt manchmal die andere Form einfachIf it underflows, it goes back to the maximum value and continues from there.
- Sie scheinen den Unterlauf mit dem negativen Überlauf verwechselt zu haben. Ein Unterlauf in ganzen Zahlen tritt ständig auf (wenn das Ergebnis ein Bruchteil ist).Nun, was primitive Integer-Typen angeht, verarbeitet Java Over / Underflow überhaupt nicht (für Float und Double ist das Verhalten unterschiedlich, es wird auf +/- unendlich gespült, genau wie IEEE-754-Mandate).
Wenn Sie zwei Ints hinzufügen, erhalten Sie keine Anzeige, wenn ein Überlauf auftritt. Eine einfache Methode zur Überprüfung auf Überlauf besteht darin, den nächstgrößeren Typ zu verwenden, um die Operation tatsächlich auszuführen, und zu überprüfen, ob das Ergebnis für den Quelltyp noch im Bereich liegt:
Was Sie anstelle der Throw-Klauseln tun würden, hängt von Ihren Anwendungsanforderungen ab (Throw, Flush auf Min / Max oder einfach alles protokollieren). Wenn Sie bei langen Vorgängen einen Überlauf feststellen möchten, haben Sie mit Grundelementen kein Glück. Verwenden Sie stattdessen BigInteger.
Edit (2014-05-21): Da diese Frage ziemlich häufig angesprochen zu werden scheint und ich das gleiche Problem selbst lösen musste, ist es ziemlich einfach, den Überlaufzustand mit der gleichen Methode zu bewerten, mit der eine CPU ihr V-Flag berechnen würde.
Es ist im Grunde ein boolescher Ausdruck, der das Vorzeichen beider Operanden sowie das Ergebnis beinhaltet:
In Java ist es einfacher, den Ausdruck (im if) auf die gesamten 32 Bits anzuwenden und das Ergebnis mit <0 zu überprüfen (dies testet effektiv das Vorzeichenbit). Das Prinzip funktioniert für alle ganzzahligen primitiven Typen genau gleich. Wenn Sie alle Deklarationen in der obigen Methode in long ändern, funktioniert es lange.
Bei kleineren Typen muss aufgrund der impliziten Konvertierung in int (Einzelheiten finden Sie im JLS für bitweise Operationen) anstelle von <0 das Vorzeichenbit explizit maskiert werden (0x8000 für kurze Operanden, 0x80 für Byteoperanden, Casts anpassen und Parameterdeklaration entsprechend):
(Beachten Sie, dass im obigen Beispiel der Ausdruck "Notwendigkeit zum Überziehen der Überlauferkennung" verwendet wird.)
Wie / warum funktionieren diese booleschen Ausdrücke? Erstens zeigt ein logisches Denken, dass ein Überlauf nur auftreten kann, wenn die Vorzeichen beider Argumente gleich sind. Denn wenn ein Argument negativ ist und eine positiv ist, ist das Ergebnis (Add) muss näher an Null ist , oder im Extremfall ein Argument Null ist , das gleiche wie das andere Argument. Da die Argumente selbst keine Überlaufbedingung erzeugen können, kann ihre Summe auch keinen Überlauf erzeugen.
Was passiert also, wenn beide Argumente das gleiche Vorzeichen haben? Schauen wir uns den Fall an, bei dem beide positiv sind: Wenn Sie zwei Argumente hinzufügen, die eine Summe erzeugen, die größer als die Typen MAX_VALUE ist, ergibt sich immer ein negativer Wert, sodass ein Überlauf auftritt, wenn arg1 + arg2> MAX_VALUE. Der maximale Wert, der sich ergeben könnte, wäre nun MAX_VALUE + MAX_VALUE (im Extremfall sind beide Argumente MAX_VALUE). Für ein Byte (Beispiel) würde dies 127 + 127 = 254 bedeuten. Wenn man die Bitdarstellungen aller Werte betrachtet, die sich aus der Addition von zwei positiven Werten ergeben können, stellt man fest, dass für diejenigen, die überlaufen (128 bis 254), Bit 7 gesetzt ist, während Bei allen, die nicht überlaufen (0 bis 127), ist Bit 7 (oberstes Vorzeichen) gelöscht. Genau das prüft der erste (rechte) Teil des Ausdrucks:
(~ s & ~ d & r) wird nur dann wahr, wenn beide Operanden (s, d) positiv und das Ergebnis (r) negativ sind (der Ausdruck funktioniert auf allen 32 Bits, aber das einzige Bit, an dem wir interessiert sind ist das oberste (Vorzeichen-) Bit, gegen das mit <0 geprüft wird.
Wenn nun beide Argumente negativ sind, kann ihre Summe niemals näher an Null liegen als eines der Argumente. Die Summe muss näher an minus unendlich sein. Der extremste Wert, den wir erzeugen können, ist MIN_VALUE + MIN_VALUE, was (wiederum für das Byte-Beispiel) zeigt, dass für jeden Wert im Bereich (-1 bis -128) das Vorzeichenbit gesetzt ist, während jeder mögliche Überlaufwert (-129 bis -256) ) hat das Vorzeichenbit gelöscht. Das Vorzeichen des Ergebnisses zeigt also erneut den Überlaufzustand. Das ist es, was die linke Hälfte (s & d & ~ r) auf den Fall prüft, in dem beide Argumente (s, d) negativ und ein positives Ergebnis sind. Die Logik entspricht weitgehend dem positiven Fall; Bei allen Bitmustern, die sich aus der Addition von zwei negativen Werten ergeben können, wird das Vorzeichenbit genau dann gelöscht, wenn ein Unterlauf aufgetreten ist.
quelle
Standardmäßig werden Javas int und long math stillschweigend um Überlauf und Unterlauf herumlaufen. (Integer-Operationen für andere Integer-Typen werden ausgeführt, indem zuerst die Operanden gemäß JLS 4.2.2 auf int oder long heraufgestuft werden .)
Wie von Java 8
java.lang.Math
bietetaddExact
,subtractExact
,multiplyExact
,incrementExact
,decrementExact
undnegateExact
statische Methoden sowohl für int und lange Argumente , die die benannte Operation durchführen, ArithmeticException bei Überlauf zu werfen. (Es gibt keine DivideExact-Methode - Sie müssen den einen Sonderfall (MIN_VALUE / -1
) selbst überprüfen .)Ab Java 8 bietet java.lang.Math auch die Möglichkeit
toIntExact
, ein Long in ein Int umzuwandeln und eine ArithmeticException auszulösen, wenn der Long-Wert nicht in ein Int passt. Dies kann nützlich sein, um z. B. die Summe der Ints mit ungeprüfter langer Mathematik zu berechnen und danntoIntExact
am Ende mit to in int umzuwandeln (aber achten Sie darauf, dass Ihre Summe nicht überläuft).Wenn Sie noch eine ältere Java-Version verwenden, bietet Google Guava statische IntMath- und LongMath- Methoden für die überprüfte Addition, Subtraktion, Multiplikation und Exponentiation (Überlauf). Diese Klassen bieten auch Methoden zur Berechnung von Fakultäten und Binomialkoeffizienten, die
MAX_VALUE
beim Überlauf zurückkehren (was weniger bequem zu überprüfen ist). Guava primitive Utility - Klassen,SignedBytes
,UnsignedBytes
,Shorts
undInts
bietencheckedCast
Methoden für größere Arten Verengung (werfen Illegal auf unter / Überlauf, nicht ArithmeticException) sowiesaturatingCast
Methoden , die RückkehrMIN_VALUE
oderMAX_VALUE
bei Überlauf.quelle
Java macht nichts mit dem Integer-Überlauf für int- oder long-primitive Typen und ignoriert den Überlauf mit positiven und negativen Ganzzahlen.
Diese Antwort beschreibt zunächst den Überlauf von Ganzzahlen, gibt ein Beispiel dafür, wie dies auch bei Zwischenwerten bei der Ausdrucksbewertung geschehen kann, und enthält dann Links zu Ressourcen, die detaillierte Techniken zum Verhindern und Erkennen eines Überlaufs von Ganzzahlen enthalten.
Ganzzahlige Arithmetik und Ausdrücke, die zu einem unerwarteten oder unerkannten Überlauf führen, sind ein häufiger Programmierfehler. Ein unerwarteter oder nicht erkannter Überlauf von Ganzzahlen ist ebenfalls ein bekanntes ausnutzbares Sicherheitsproblem, insbesondere da es Array-, Stapel- und Listenobjekte betrifft.
Ein Überlauf kann entweder in positiver oder negativer Richtung auftreten, wobei der positive oder negative Wert über den Maximal- oder Minimalwerten für den betreffenden primitiven Typ liegen würde. Ein Überlauf kann in einem Zwischenwert während der Ausdrucks- oder Operationsbewertung auftreten und sich auf das Ergebnis eines Ausdrucks oder einer Operation auswirken, bei der erwartet wird, dass der Endwert innerhalb des Bereichs liegt.
Manchmal wird ein negativer Überlauf fälschlicherweise als Unterlauf bezeichnet. Unterlauf ist das, was passiert, wenn ein Wert näher an Null liegt, als es die Darstellung zulässt. Der Unterlauf tritt in der Ganzzahlarithmetik auf und wird erwartet. Ein ganzzahliger Unterlauf tritt auf, wenn eine ganzzahlige Auswertung zwischen -1 und 0 oder 0 und 1 liegt. Was ein gebrochenes Ergebnis wäre, wird auf 0 abgeschnitten. Dies ist normal und wird mit ganzzahliger Arithmetik erwartet und nicht als Fehler betrachtet. Dies kann jedoch dazu führen, dass Code eine Ausnahme auslöst. Ein Beispiel ist eine Ausnahme "ArithmeticException: / by zero", wenn das Ergebnis eines ganzzahligen Unterlaufs als Divisor in einem Ausdruck verwendet wird.
Betrachten Sie den folgenden Code:
Dies führt dazu, dass x 0 zugewiesen wird und die anschließende Auswertung von bigValue / x eine Ausnahme "ArithmeticException: / by zero" (dh durch Null teilen) auslöst, anstatt y den Wert 2 zuzuweisen.
Das erwartete Ergebnis für x wäre 858.993.458, was weniger als der maximale int-Wert von 2.147.483.647 ist. Das Zwischenergebnis aus der Auswertung von Integer.MAX_Value * 2 wäre jedoch 4.294.967.294, was den maximalen int-Wert überschreitet und gemäß 2s-Komplement-Integer-Darstellungen -2 beträgt. Die nachfolgende Auswertung von -2 / 5 ergibt 0, die x zugewiesen wird.
Neuanordnen des Ausdrucks für die Berechnung von x in einen Ausdruck, der bei Auswertung vor dem Multiplizieren den folgenden Code teilt:
führt dazu, dass x 858.993.458 und y 2 zugewiesen wird, was erwartet wird.
Das Zwischenergebnis von bigValue / 5 ist 429.496.729, was den Maximalwert für ein int nicht überschreitet. Die nachfolgende Auswertung von 429.496.729 * 2 überschreitet nicht den Maximalwert für ein int und das erwartete Ergebnis wird x zugewiesen. Die Auswertung für y wird dann nicht durch Null geteilt. Die Auswertungen für x und y funktionieren wie erwartet.
Java-Ganzzahlwerte werden als 2s-Komplement-Ganzzahldarstellungen mit Vorzeichen gespeichert und verhalten sich entsprechend. Wenn ein resultierender Wert größer oder kleiner als die maximalen oder minimalen Ganzzahlwerte wäre, ergibt sich stattdessen der Komplement-Ganzzahlwert einer 2. In Situationen, die nicht ausdrücklich für die Verwendung des 2s-Komplementverhaltens ausgelegt sind, was die meisten gewöhnlichen ganzzahligen arithmetischen Situationen sind, verursacht der resultierende 2s-Komplementwert eine Programmierlogik oder einen Berechnungsfehler, wie im obigen Beispiel gezeigt. Ein ausgezeichneter Wikipedia-Artikel beschreibt hier binäre Ganzzahlen mit 2 Komplimenten: Zweierkomplement - Wikipedia
Es gibt Techniken, um einen unbeabsichtigten Ganzzahlüberlauf zu vermeiden. Techinques können als Pre-Condition-Tests, Upcasting und BigInteger kategorisiert werden.
Das Testen vor der Bedingung umfasst das Untersuchen der Werte, die in eine arithmetische Operation oder einen arithmetischen Ausdruck eingehen, um sicherzustellen, dass bei diesen Werten kein Überlauf auftritt. Programmierung und Design müssen Tests erstellen, die sicherstellen, dass Eingabewerte keinen Überlauf verursachen, und dann bestimmen, was zu tun ist, wenn Eingabewerte auftreten, die einen Überlauf verursachen.
Upcasting umfasst die Verwendung eines größeren primitiven Typs zum Ausführen der arithmetischen Operation oder des Ausdrucks und das anschließende Bestimmen, ob der resultierende Wert über den Maximal- oder Minimalwerten für eine Ganzzahl liegt. Selbst beim Upcasting ist es immer noch möglich, dass der Wert oder ein Zwischenwert in einer Operation oder einem Ausdruck über den Maximal- oder Minimalwerten für den Upcast-Typ liegt und einen Überlauf verursacht, der ebenfalls nicht erkannt wird und unerwartete und unerwünschte Ergebnisse verursacht. Durch Analyse oder Vorbedingungen kann es möglich sein, einen Überlauf mit Upcasting zu verhindern, wenn eine Prävention ohne Upcasting nicht möglich oder praktisch ist. Wenn die fraglichen Ganzzahlen bereits lange primitive Typen sind, ist ein Upcasting mit primitiven Typen in Java nicht möglich.
Die BigInteger-Technik umfasst die Verwendung von BigInteger für die arithmetische Operation oder den Ausdruck unter Verwendung von Bibliotheksmethoden, die BigInteger verwenden. BigInteger läuft nicht über. Bei Bedarf wird der gesamte verfügbare Speicher verwendet. Seine arithmetischen Methoden sind normalerweise nur geringfügig weniger effizient als ganzzahlige Operationen. Es ist weiterhin möglich, dass ein Ergebnis mit BigInteger über den Maximal- oder Minimalwerten für eine Ganzzahl liegt. In der zum Ergebnis führenden Arithmetik tritt jedoch kein Überlauf auf. Programmierung und Design müssen noch festlegen, was zu tun ist, wenn ein BigInteger-Ergebnis über den Maximal- oder Minimalwerten für den gewünschten primitiven Ergebnistyp liegt, z. B. int oder long.
Das CERT-Programm des Carnegie Mellon Software Engineering Institute und Oracle haben eine Reihe von Standards für die sichere Java-Programmierung erstellt. In den Standards sind Techniken zum Verhindern und Erkennen eines Ganzzahlüberlaufs enthalten. Der Standard wird hier als frei zugängliche Online-Ressource veröffentlicht: Der CERT Oracle Secure Coding Standard für Java
Der Abschnitt des Standards, der praktische Beispiele für Codierungstechniken zum Verhindern oder Erkennen eines Ganzzahlüberlaufs beschreibt und enthält, ist hier: NUM00-J. Erkennen oder verhindern Sie einen Ganzzahlüberlauf
Buchform und PDF-Form des CERT Oracle Secure Coding Standard für Java sind ebenfalls verfügbar.
quelle
Nachdem ich selbst auf dieses Problem gestoßen bin, ist hier meine Lösung (sowohl für die Multiplikation als auch für die Addition):
Fühlen Sie sich frei zu korrigieren, wenn falsch oder vereinfacht werden kann. Ich habe einige Tests mit der Multiplikationsmethode durchgeführt, hauptsächlich Randfälle, aber es könnte immer noch falsch sein.
quelle
int*int
ich würde denken, einfach zu werfenlong
und zu sehen, ob das Ergebnis passt,int
wäre der schnellste Ansatz. Fürlong*long
, wenn man normalisieren Operanden positiv zu sein, kann man jeweils in oberen aufgeteilt und 32-Bit - Hälften senken, fördert jede Hälfte zu lang (vorsichtig sein Zeichen Erweiterungen!) Und dann zwei Teilprodukte berechnen [einer der oberen Hälften sollten Null sein].Es gibt Bibliotheken, die sichere arithmetische Operationen bereitstellen und den Ganzzahlüberlauf / -unterlauf überprüfen. Zum Beispiel gibt Guavas IntMath.checkedAdd (int a, int b) die Summe von
a
und zurückb
, sofern es nicht überläuft, und wirft,ArithmeticException
wenn esa + b
in vorzeichenbehafteterint
Arithmetik überläuft .quelle
Math
enthält die Klasse ähnlichen Code.Es wickelt sich herum.
z.B:
druckt
quelle
Ich denke, Sie sollten so etwas verwenden und es heißt Upcasting:
Lesen Sie hier weiter: Erkennen oder verhindern Sie einen Ganzzahlüberlauf
Es ist eine ziemlich zuverlässige Quelle.
quelle
Es macht nichts - der Unter- / Überlauf passiert einfach.
Ein "-1", das das Ergebnis einer übergelaufenen Berechnung ist, unterscheidet sich nicht von dem "-1", das sich aus anderen Informationen ergibt. Sie können also nicht anhand eines Status oder anhand eines Werts feststellen, ob dieser übergelaufen ist.
Sie können jedoch bei Ihren Berechnungen klug sein, um einen Überlauf zu vermeiden, wenn dies wichtig ist, oder zumindest zu wissen, wann er eintreten wird. Wie ist deine Situation?
quelle
quelle
Ich denke das sollte in Ordnung sein.
quelle
Es gibt einen Fall, der oben nicht erwähnt wurde:
wird herstellen:
Dieser Fall wurde hier diskutiert: Ein ganzzahliger Überlauf erzeugt Null.
quelle