Java >> Java Tutorial >  >> Java

Wie geht Java mit Integer-Unterläufen und -Überläufen um und wie würden Sie darauf prüfen?

Wenn es überläuft, geht es zurück auf den Minimalwert und läuft von dort weiter. Wenn es unterläuft, geht es zurück auf den Maximalwert und fährt von dort aus fort.

Das kannst du 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 ersetzen bis long um die gleichen Prüfungen für long durchzuführen )

Wenn Sie der Meinung sind, dass dies häufiger vorkommt, sollten Sie einen Datentyp oder ein Objekt verwenden, das größere Werte speichern kann, z. long oder vielleicht java.math.BigInteger . Der letzte läuft praktisch nicht über, der verfügbare JVM-Speicher ist die Grenze.

Wenn Sie bereits Java8 verwenden, können Sie den neuen Math#addExact() verwenden und Math#subtractExact() Methoden, die einen ArithmeticException auslösen bei Überlauf.

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

Natürlich könntest du sie auch gleich verwenden, anstatt sie in einem boolean zu verstecken Utility-Methode.


Nun, was primitive Integer-Typen betrifft, handhabt Java Über-/Unterlauf ü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, um auf Überlauf zu prüfen, besteht darin, den nächstgrößeren Typ zu verwenden, um die Operation tatsächlich auszuführen, und zu prüfen, ob das Ergebnis noch im Bereich für den Quelltyp 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 den Anforderungen Ihrer Anwendung ab (throw, bündig auf min/max oder einfach nur protokollieren). Wenn Sie bei langen Operationen einen Überlauf erkennen möchten, haben Sie mit Primitiven kein Glück, verwenden Sie stattdessen BigInteger.

Bearbeiten (2014-05-21):Da diese Frage anscheinend ziemlich häufig gestellt wird und ich das gleiche Problem selbst lösen musste, ist es recht einfach, die Überlaufbedingung mit der gleichen Methode auszuwerten, 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 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 (in if) auf die gesamten 32 Bits anzuwenden und das Ergebnis mit <0 zu überprüfen (dies testet effektiv das Vorzeichenbit). Das Prinzip funktioniert genau gleich für alle ganzzahligen primitiven Typen , das Ändern aller Deklarationen in der obigen Methode in lang lässt es lange funktionieren.

Bei kleineren Typen muss aufgrund der impliziten Konvertierung in int (siehe JLS für bitweise Operationen für Details) statt <0 zu prüfen, die Prüfung das Vorzeichenbit explizit maskieren (0x8000 für kurze Operanden, 0x80 für Byte-Operanden, Umwandlungen anpassen und Parameterdeklaration entsprechend):

/**
 * 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 das obige Beispiel die Ausdrucksnotwendigkeit für subtract verwendet Überlauferkennung)

Wie/warum funktionieren diese booleschen Ausdrücke? Erstens zeigt etwas logisches Denken, dass ein Überlauf nur kann auftreten, wenn die Vorzeichen beider Argumente gleich sind. Denn wenn ein Argument negativ und eines positiv ist, muss das Ergebnis (von add) müssen näher an Null sein, oder im Extremfall ist ein Argument gleich Null wie das andere Argument. Da die Argumente allein nicht können eine Überlaufbedingung erzeugen, ihre Summe kann auch keinen Überlauf erzeugen.

Was passiert also, wenn beide Argumente das gleiche Vorzeichen haben? Schauen wir uns den Fall an, dass beide positiv sind:Das Addieren von zwei Argumenten, die eine größere Summe als die Typen MAX_VALUE erzeugen, ergibt immer einen negativen Wert, sodass ein Überlauf auftritt, wenn arg1 + arg2> MAX_VALUE. Nun wäre der maximal mögliche Wert MAX_VALUE + MAX_VALUE (im Extremfall sind beide Argumente MAX_VALUE). Für ein Byte (Beispiel) würde das 127 + 127 =254 bedeuten. Betrachtet man die Bitdarstellungen aller Werte, die sich aus der Addition zweier positiver Werte ergeben können, stellt man fest, dass diejenigen, die überlaufen (128 bis 254), alle Bit 7 gesetzt haben, 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:

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 auf allen 32 Bits, aber das einzige Bit, an dem wir interessiert sind, ist das oberste (Vorzeichen-) Bit, das von der überprüft wird <0).

Wenn nun beide Argumente negativ sind, kann ihre Summe nie näher an Null sein als irgendeines 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 (wieder für ein Byte-Beispiel) zeigt, dass für jeden Wert im Bereich (-1 bis -128) das Vorzeichenbit gesetzt ist, während jeder mögliche überlaufende Wert (-129 bis -256 ) hat das Vorzeichenbit gelöscht. Das Vorzeichen des Ergebnisses offenbart also wieder die Überlaufbedingung. Das prüft die linke Hälfte (s &d &~r) für den Fall, dass beide Argumente (s, d) negativ sind und ein Ergebnis positiv ist. Die Logik entspricht weitgehend dem positiven Fall; bei allen Bitmustern, die sich aus der Addition zweier negativer Werte ergeben können, wird das Vorzeichenbit gelöscht wenn und nur wenn ein Unterlauf ist aufgetreten.


Standardmäßig werden die Int- und Long-Mathematik von Java bei Überlauf und Unterlauf stillschweigend umgangen. (Integer-Operationen mit anderen Integer-Typen werden durchgeführt, indem zuerst die Operanden gemäß JLS 4.2.2 in 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 benannte Operation ausführen und ArithmeticException bei Überlauf auslösen. (Es gibt keine divideExact-Methode – Sie müssen den einen Sonderfall überprüfen (MIN_VALUE / -1 ) selbst.)

Ab Java 8 stellt java.lang.Math auch toIntExact zur Verfügung um einen long in einen int umzuwandeln, wobei ArithmeticException ausgelöst wird, wenn der Wert von long nicht in einen int passt. Dies kann z. Berechnen der Summe von Ints mit ungeprüfter langer Mathematik und dann mit toIntExact am Ende in int umzuwandeln (aber achten Sie darauf, dass Ihre Summe nicht überläuft).

Wenn Sie noch eine ältere Version von Java verwenden, bietet Google Guava die statischen Methoden IntMath und LongMath für geprüfte Addition, Subtraktion, Multiplikation und Potenzierung (Throwing on Overflow). Diese Klassen bieten auch Methoden zum Berechnen von Fakultäten und Binomialkoeffizienten, die MAX_VALUE zurückgeben auf Überlauf (was weniger bequem zu überprüfen ist). Guavas primitive Hilfsklassen, SignedBytes , UnsignedBytes , Shorts und Ints , geben Sie checkedCast an Methoden zum Eingrenzen größerer Typen (IllegalArgumentException bei Unter-/Überlauf auslösen, nicht ArithmeticException) sowie saturatingCast Methoden, die MIN_VALUE zurückgeben oder MAX_VALUE bei Überlauf.


Java-Tag