it-swarm.com.de

Wie geht Java mit Integer-Unterläufen und -Überflüssen um und wie würden Sie darauf achten?

Wie geht Java mit Integer-Unterläufen und -Überläufen um?

Davon ausgehend, wie würden Sie prüfen, ob dies der Fall ist?

195
KushalP

Wenn es überläuft, geht es zurück auf den Minimalwert und geht von dort weiter. Wenn es unterfließt, geht es zurück auf den Maximalwert und geht von dort weiter.

Sie können dies vorher wie folgt überprüfen:

public static boolean willAdditionOverflow(int left, int right) {
    if (right < 0 && right != Integer.MIN_VALUE) {
        return willSubtractionOverflow(left, -right);
    } else {
        return (~(left ^ right) & (left ^ (left + right))) < 0;
    }
}

public static boolean willSubtractionOverflow(int left, int right) {
    if (right < 0) {
        return willAdditionOverflow(left, -right);
    } else {
        return ((left ^ right) & (left ^ (left - right))) < 0;
    }
}

(Sie können int durch long ersetzen, um dieselben Prüfungen für long durchzuführen.)

Wenn Sie der Meinung sind, dass dies öfter vorkommt, sollten Sie einen Datentyp oder ein Objekt verwenden, das größere Werte speichern kann, z. long oder vielleicht Java.math.BigInteger . Der letzte überläuft nicht, praktisch ist der verfügbare JVM-Speicher das Limit.


Wenn Sie sich bereits auf Java8 befinden, können Sie die neuen Methoden Math#addExact() und Math#subtractExact() verwenden, die bei einem Überlauf eine ArithmeticException auslösen.

public static boolean willAdditionOverflow(int left, int right) {
    try {
        Math.addExact(left, right);
        return false;
    } catch (ArithmeticException e) {
        return true;
    }
}

public static boolean willSubtractionOverflow(int left, int right) {
    try {
        Math.subtractExact(left, right);
        return false;
    } catch (ArithmeticException e) {
        return true;
    }
}

Den Quellcode finden Sie hier und hier .

Natürlich können Sie sie auch direkt verwenden, anstatt sie in einer boolean-Utility-Methode zu verbergen.

198
BalusC

Nun, soweit es primitive Integer-Typen betrifft, verarbeitet Java Über-/Unterlauf überhaupt nicht (bei float und double ist das Verhalten anders, es wird wie bei IEEE-754-Mandaten auf +/- unendlich gespült).

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:

public int addWithOverflowCheck(int a, int b) {
    // the cast of a is required, to make the + work with long precision,
    // if we just added (a + b) the addition would use int precision and
    // the result would be cast to long afterwards!
    long result = ((long) a) + b;
    if (result > Integer.MAX_VALUE) {
         throw new RuntimeException("Overflow occured");
    } else if (result < Integer.MIN_VALUE) {
         throw new RuntimeException("Underflow occured");
    }
    // at this point we can safely cast back to int, we checked before
    // that the value will be withing int's limits
    return (int) result;
}

Was Sie anstelle der Throw-Klauseln tun würden, hängt von Ihren Anwendungsanforderungen ab (Throw, Flush to Min/Max oder einfach nur loggen, was auch immer). Wenn Sie einen Überlauf bei langen Operationen erkennen möchten, haben Sie mit Primitiven kein Glück. Verwenden Sie stattdessen BigInteger.


Edit (2014-05-21): Da diese Frage anscheinend häufig gestellt wird und ich das gleiche Problem selbst lösen musste, ist es recht einfach, den Überlaufzustand mit der gleichen Methode zu bewerten, mit der eine CPU ihr V-Flag berechnet.

Es ist im Grunde ein boolescher Ausdruck, der sowohl das Vorzeichen beider Operanden als auch das Ergebnis enthält:

/**
 * Add two int's with overflow detection (r = s + d)
 */
public static int add(final int s, final int d) throws ArithmeticException {
    int r = s + d;
    if (((s & d & ~r) | (~s & ~d & r)) < 0)
        throw new ArithmeticException("int overflow add(" + s + ", " + d + ")");    
    return r;
}

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 genauso für alle ganzzahligen Grundtypen, wenn alle Deklarationen in der obigen Methode auf long geändert werden, funktioniert es lange.

Bei kleineren Typen muss aufgrund der impliziten Konvertierung in int (Details finden Sie in der JLS für bitweise Operationen) das Vorzeichenbit nicht <0, sondern explizit maskiert werden (0x8000 für kurze Operanden, 0x80 für Byte-Operanden, Casts anpassen) und entsprechende Parameterdeklaration):

/**
 * Subtract two short's with overflow detection (r = d - s)
 */
public static short sub(final short d, final short s) throws ArithmeticException {
    int r = d - s;
    if ((((~s & d & ~r) | (s & ~d & r)) & 0x8000) != 0)
        throw new ArithmeticException("short overflow sub(" + s + ", " + d + ")");
    return (short) r;
}

(Beachten Sie, dass im obigen Beispiel der Ausdruck need für subtract overflow detection 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 und ein Argument positiv ist, ist das Ergebnis (von add) muss näher bei Null, oder im Extremfall ist ein Argument gleich Null wie das andere Argument. Da die Argumente selbst nicht eine Überlaufbedingung erzeugen, kann ihre Summe auch keinen Überlauf erzeugen.

Was passiert also, wenn beide Argumente dasselbe Vorzeichen haben? Betrachten wir den Fall, dass beide positiv sind: Das Hinzufügen von zwei Argumenten, die eine Summe größer als die Typen MAX_VALUE ergeben, ergibt immer einen negativen Wert, sodass ein Überlauf auftritt if arg1 + arg2> MAX_VALUE. Jetzt wäre der Maximalwert, der resultieren könnte, MAX_VALUE + MAX_VALUE (im Extremfall sind beide Argumente MAX_VALUE). Für ein Byte (Beispiel) würde dies 127 + 127 = 254 bedeuten. Betrachtet man die Bitdarstellungen aller Werte, die sich aus der Addition von zwei positiven Werten ergeben können, so 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 Zeichen) gelöscht. Genau das überprüft der erste (rechte) Teil des Ausdrucks:

if (((s & d & ~r) | (~s & ~d & r)) < 0)

(~ s & ~ d & r) wird wahr, nur wenn, beide Operanden (s, d) sind positiv und das Ergebnis (r) ist negativ (der Ausdruck funktioniert mit allen 32 Bits, aber dem Das einzige Bit, an dem wir interessiert sind, ist das oberste (Vorzeichen-) Bit, das mit dem Wert <0) verglichen wird.

Wenn nun beide Argumente negativ sind, kann ihre Summe niemals näher an Null sein 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 (erneut für das Byte-Beispiel) zeigt, dass für jeden Wert im Bereich (-1 bis -128) das Vorzeichenbit gesetzt ist, während ein möglicher Überlaufwert (-129 bis -256) vorliegt ) hat das Vorzeichenbit gelöscht. Das Vorzeichen des Ergebnisses zeigt also wieder den Überlaufzustand. Das ist, was die linke Hälfte (s & d & ~ r) für den Fall prüft, dass beide Argumente (s, d) negativ sind und ein positives Ergebnis. Die Logik entspricht weitgehend dem positiven Fall; Bei allen Bitmustern, die aus dem Hinzufügen von zwei negativen Werten resultieren können, wird das Vorzeichenbit gelöscht genau dann, wenn ein Unterlauf aufgetreten ist.

64
Durandal

Standardmäßig werden Javas int- und long-Mathematik beim Über- und Unterlauf stillschweigend umgangen. (Integer-Operationen für andere Integer-Typen werden ausgeführt, indem die Operanden zuerst nach JLS 4.2.2 auf int oder long hochgestuft werden.)

Ab Java 8, Java.lang.Math liefert addExact , subtractExact , multiplyExact , incrementExact , decrementExact und negateExact statische Methoden für int- und long-Argumente, die die genannte Operation ausführen, ArithmeticException bei Überlauf auslösen. (Es gibt keine divideExact-Methode - Sie müssen den einen Sonderfall prüfen (MIN_VALUE / -1) dich selber.)

Ab Java 8 bietet Java.lang.Math auch toIntExact , um ein Long auf ein int zu setzen und ArithmeticException auszulösen, wenn der Wert des Longs nicht stimmt fit in a int. Dies kann nützlich sein, um z. B. die Summe der Ints mit ungeprüfter langer Mathematik zu berechnen und am Ende mit toIntExact nach int umzuwandeln (aber achten Sie darauf, dass Ihre Summe nicht überläuft).

Wenn Sie noch eine ältere Java-Version verwenden, bietet Google Guava IntMath und LongMath statische Methoden für die überprüfte Addition, Subtraktion, Multiplikation und Exponentiation (Auslösen bei Überlauf). Diese Klassen bieten auch Methoden zur Berechnung von Fakultäten und Binomialkoeffizienten, die MAX_VALUE bei Überlauf (was weniger bequem zu überprüfen ist). Die primitiven Hilfsklassen von Guava, SignedBytes, UnsignedBytes, Shorts und Ints, stellen checkedCast Methoden zum Eingrenzen größerer Typen bereit (wirft IllegalArgumentException auf under/overflow, not ArithmeticException) sowie saturatingCast Methoden, die MIN_VALUE oder MAX_VALUE bei Überlauf.

31
Jeffrey Bosboom

Java macht nichts mit dem Integer-Überlauf für int oder lange primitive Typen und ignoriert den Überlauf mit positiven und negativen Ganzzahlen. 

Diese Antwort beschreibt zunächst den Integer-Überlauf, gibt ein Beispiel dafür, wie dies auch bei Zwischenwerten in der Ausdrucksbewertung möglich ist, und gibt dann Links zu Ressourcen, die detaillierte Techniken zum Verhindern und Erkennen eines Integer-Überlaufs enthalten. 

Ganzzahlige Arithmetik und Ausdrücke, die zu einem unerwarteten oder nicht erkannten Überlauf führen, sind ein häufiger Programmierfehler. Unerwarteter oder unerkannter Ganzzahlüberlauf ist ebenfalls ein bekanntes ausnutzbares Sicherheitsproblem, zumal Array-, Stack- und Listenobjekte betroffen sind. 

Ein Überlauf kann entweder in eine positive oder negative Richtung auftreten, in der der positive oder negative Wert über dem maximalen oder minimalen Wert für den fraglichen primitiven Typ liegt. Ein Überlauf kann während der Ausdrucks- oder Operationsbewertung in einem Zwischenwert auftreten und das Ergebnis eines Ausdrucks oder einer Operation beeinflussen, bei dem der Endwert voraussichtlich innerhalb des Bereichs liegt. 

Manchmal wird negativer Überlauf fälschlicherweise als Unterlauf bezeichnet. Unterlauf ist, was passiert, wenn ein Wert näher an Null liegt, als die Darstellung zulässt. Unterlauf tritt in Ganzzahlarithmetik auf und wird erwartet. Ein ganzer Unterlauf tritt auf, wenn eine Ganzzahlauswertung zwischen -1 und 0 oder 0 und 1 liegen würde. Was ein gebrochenes Ergebnis wäre, wird auf 0 abgeschnitten. Dies ist normal und wird mit Ganzzahlarithmetik erwartet und wird nicht als Fehler betrachtet. Es kann jedoch dazu führen, dass Code eine Ausnahme auslöst. Ein Beispiel ist die Ausnahme "ArithmeticException:/by zero", wenn das Ergebnis des Ganzzahlunterlaufs als Divisor in einem Ausdruck verwendet wird. 

Betrachten Sie den folgenden Code:

int bigValue = Integer.MAX_VALUE;
int x = bigValue * 2 / 5;
int y = bigValue / x;

was zur Folge hat, dass x 0 zugewiesen wird, und die nachfolgende Auswertung von bigValue/x löst die Ausnahme "ArithmeticException:/by zero" aus (d. h. Division durch Null), statt 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 Bewertung von Integer.MAX_Value * 2 wäre jedoch 4.294.967.294, was den maximalen Int-Wert überschreitet und -2 gemäß 2s-Komplement-Integer-Darstellungen beträgt. Die nachfolgende Auswertung von -2/5 ergibt 0 und wird x zugewiesen. 

Neuanordnung des Ausdrucks für die Berechnung von x zu einem Ausdruck, der bei der Auswertung den folgenden Code vor der Multiplikation dividiert:

int bigValue = Integer.MAX_VALUE;
int x = bigValue / 5 * 2;
int y = bigValue / x;

führt dazu, dass x 858.993.458 zugewiesen wird 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 teilt sich dann nicht durch Null. Die Bewertungen für x und y funktionieren wie erwartet.

Java Integer-Werte werden als 2s-Komplement-Integer-Integer-Darstellungen gespeichert und verhalten sich entsprechend. Wenn ein resultierender Wert größer oder kleiner als der maximale oder minimale ganzzahlige Wert ist, wird stattdessen ein ganzzahliger 2-Komplementwert ausgegeben. In Situationen, die nicht ausdrücklich für die Verwendung des 2s-Komplementverhaltens ausgelegt sind, was die meisten ganzzahligen arithmetischen Situationen ist, führt der resultierende 2s-Komplementwert zu einer Programmierlogik oder einem Berechnungsfehler, wie im obigen Beispiel gezeigt. Ein ausgezeichneter Wikipedia-Artikel beschreibt hier 2s-Komplemente binärer Ganzzahlen: Two-Complement - Wikipedia

Es gibt Techniken, um einen unbeabsichtigten Integer-Überlauf zu vermeiden. Techinques können als Tests mit Vorbedingungen, Upcasting und BigInteger kategorisiert werden. 

Das Testen vor Bedingungen umfasst das Untersuchen der Werte, die in eine arithmetische Operation oder einen arithmetischen Ausdruck einfließen, um sicherzustellen, dass bei diesen Werten kein Überlauf auftritt. Die Programmierung und das Design müssen Tests erstellen, die sicherstellen, dass Eingabewerte keinen Überlauf verursachen, und bestimmen dann, was zu tun ist, wenn Eingabewerte auftreten, die einen Überlauf verursachen. 

Das Upcasting umfasst das Verwenden eines größeren primitiven Typs zum Ausführen der arithmetischen Operation oder des arithmetischen Ausdrucks und dann das Bestimmen, ob der resultierende Wert außerhalb der maximalen oder minimalen Werte für eine ganze Zahl liegt. Selbst beim Upcasting ist es immer noch möglich, dass der Wert oder ein Zwischenwert in einer Operation oder einem Ausdruck die Maximal- oder Minimalwerte für den Upcast-Typ überschreitet 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 beim Upcasting zu verhindern, wenn eine Prävention ohne Upcasting nicht möglich oder nicht praktikabel ist. Wenn die betreffenden Ganzzahlen bereits lange primitive Typen sind, ist das 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 Speicherplatz verwendet. Seine arithmetischen Methoden sind normalerweise nur geringfügig weniger effizient als Ganzzahloperationen. Es ist immer noch möglich, dass ein Ergebnis, das BigInteger verwendet, die maximalen oder minimalen Werte für eine ganze Zahl überschreitet. In der zum Ergebnis führenden Arithmetik tritt jedoch kein Überlauf auf. Die Programmierung und das Design müssen noch bestimmen, was zu tun ist, wenn ein BigInteger-Ergebnis über den Maximal- oder Minimalwerten für den gewünschten primitiven Ergebnistyp liegt, z.

Das CERT-Programm des Carnegie Mellon Software Engineering Institute und Oracle haben eine Reihe von Standards für die sichere Java-Programmierung geschaffen. In den Standards enthalten sind Techniken zum Verhindern und Erkennen eines Integer-Überlaufs. Der Standard wird hier als frei zugängliche Online-Ressource veröffentlicht: Der CERT Oracle Secure Coding Standard für Java

Der Abschnitt der Norm, der praktische Beispiele für Codierungstechniken zum Verhindern oder Erkennen eines Integer-Überlaufs beschreibt und enthält, ist hier: NUM00-J. Integer-Überlauf erkennen oder verhindern

Buchform und PDF -Form des CERT Oracle Secure Coding Standard für Java sind ebenfalls verfügbar.

Book form and PDF form of The CERT Oracle Secure Coding Standard for Java are also available.

31
Jim

Da ich dieses Problem irgendwie selbst getroffen habe, hier meine Lösung (sowohl für die Multiplikation als auch für die Addition):

static boolean wouldOverflowOccurwhenMultiplying(int a, int b) {
    // If either a or b are Integer.MIN_VALUE, then multiplying by anything other than 0 or 1 will result in overflow
    if (a == 0 || b == 0) {
        return false;
    } else if (a > 0 && b > 0) { // both positive, non zero
        return a > Integer.MAX_VALUE / b;
    } else if (b < 0 && a < 0) { // both negative, non zero
        return a < Integer.MAX_VALUE / b;
    } else { // exactly one of a,b is negative and one is positive, neither are zero
        if (b > 0) { // this last if statements protects against Integer.MIN_VALUE / -1, which in itself causes overflow.
            return a < Integer.MIN_VALUE / b;
        } else { // a > 0
            return b < Integer.MIN_VALUE / a;
        }
    }
}

boolean wouldOverflowOccurWhenAdding(int a, int b) {
    if (a > 0 && b > 0) {
        return a > Integer.MAX_VALUE - b;
    } else if (a < 0 && b < 0) {
        return a < Integer.MIN_VALUE - b;
    }
    return false;
}

fühlen Sie sich frei zu korrigieren, wenn falsch oder vereinfacht werden. Ich habe einige Tests mit der Multiplikationsmethode durchgeführt, meistens Edge-Fälle, aber es könnte trotzdem falsch sein.

11
fragorl

Es gibt Bibliotheken, die sichere Rechenoperationen bereitstellen, die den Überlauf/Unterlauf von Ganzzahlen prüfen. Beispielsweise liefert Guavas IntMath.checkedAdd (int a, int b) die Summe von a und b, sofern diese nicht überläuft, und ArithmeticException, wenn a + b in signierter int-Arithmetik überläuft.

8
reprogrammer

Es wickelt sich herum.

z.B:

public class Test {

    public static void main(String[] args) {
        int i = Integer.MAX_VALUE;
        int j = Integer.MIN_VALUE;

        System.out.println(i+1);
        System.out.println(j-1);
    }
}

druckt

-2147483648
2147483647
6
Peter Tillemans

Ich denke, du solltest so etwas verwenden und es heißt Upcasting:

public int multiplyBy2(int x) throws ArithmeticException {
    long result = 2 * (long) x;    
    if (result > Integer.MAX_VALUE || result < Integer.MIN_VALUE){
        throw new ArithmeticException("Integer overflow");
    }
    return (int) result;
}

Sie können hier weiterlesen: Integer-Überlauf erkennen oder verhindern

Es ist eine ziemlich zuverlässige Quelle.

5
Dusan

Es tut nichts - der Unter-/Überlauf passiert einfach.

Eine "-1", die das Ergebnis einer überlaufenen Berechnung ist, unterscheidet sich nicht von der "-1", die sich aus anderen Informationen ergibt. Sie können also nicht anhand eines Status oder durch Überprüfen eines bestimmten Werts feststellen, ob er überläuft.

Sie können jedoch mit Ihren Berechnungen klug umgehen, um einen Überlauf zu vermeiden, wenn es darauf ankommt, oder zumindest wissen, wann dies geschieht. Wie ist deine Situation?

3
Sean Owen

Es gibt einen Fall, der oben nicht erwähnt wurde:

int res = 1;
while (res != 0) {
    res *= 2;

}
System.out.println(res);

wird herstellen:

0

Dieser Fall wurde hier besprochen: Integer-Überlauf erzeugt Null.

0
lobzik
static final int safeAdd(int left, int right)
                 throws ArithmeticException {
  if (right > 0 ? left > Integer.MAX_VALUE - right
                : left < Integer.MIN_VALUE - right) {
    throw new ArithmeticException("Integer overflow");
  }
  return left + right;
}

static final int safeSubtract(int left, int right)
                 throws ArithmeticException {
  if (right > 0 ? left < Integer.MIN_VALUE + right
                : left > Integer.MAX_VALUE + right) {
    throw new ArithmeticException("Integer overflow");
  }
  return left - right;
}

static final int safeMultiply(int left, int right)
                 throws ArithmeticException {
  if (right > 0 ? left > Integer.MAX_VALUE/right
                  || left < Integer.MIN_VALUE/right
                : (right < -1 ? left > Integer.MIN_VALUE/right
                                || left < Integer.MAX_VALUE/right
                              : right == -1
                                && left == Integer.MIN_VALUE) ) {
    throw new ArithmeticException("Integer overflow");
  }
  return left * right;
}

static final int safeDivide(int left, int right)
                 throws ArithmeticException {
  if ((left == Integer.MIN_VALUE) && (right == -1)) {
    throw new ArithmeticException("Integer overflow");
  }
  return left / right;
}

static final int safeNegate(int a) throws ArithmeticException {
  if (a == Integer.MIN_VALUE) {
    throw new ArithmeticException("Integer overflow");
  }
  return -a;
}
static final int safeAbs(int a) throws ArithmeticException {
  if (a == Integer.MIN_VALUE) {
    throw new ArithmeticException("Integer overflow");
  }
  return Math.abs(a);
}
0
user4267316

Ich denke, das sollte in Ordnung sein.

static boolean addWillOverFlow(int a, int b) {
    return (Integer.signum(a) == Integer.signum(b)) && 
            (Integer.signum(a) != Integer.signum(a+b)); 
}
0
John Woo