18.4 Genauigkeit, Wertebereich eines Typs und Überlaufkontrolle *
Für jeden primitiven Datentyp gibt es in Java eine eigene Klasse mit diversen Methoden und Konstanten. Die Klassen Byte, Short, Integer, Long, Float und Double besitzen die Konstanten MIN_VALUE und MAX_VALUE für den minimalen und maximalen Wertebereich. Die Klassen Float und Double verfügen zusätzlich über die wichtigen Konstanten NEGATIVE_INFINITY und POSITIVE_INFINITY für minus und plus unendlich und NaN (Not a Number, undefiniert).
Hinweis |
Integer.MIN_VALUE steht mit –2147483648 für den kleinsten Wert, den die Ganzzahl annehmen kann. Double.MIN_VALUE steht jedoch für die kleinste positive Zahl (beste Näherung an 0), die ein Double darstellen kann (4.9E–324). |
Wenn uns beim Wort double im Vergleich zu float eine »doppelte Genauigkeit« über die Lippen kommt, müssen wir mit der Aussage vorsichtig sein, denn double bietet zumindest nach der Anzahl der Bits eine mehr als doppelt so präzise Mantisse. Über die Anzahl der Nachkommastellen sagt das jedoch direkt nichts aus.
Bastelaufgabe |
Warum sind die Ausgaben so, wie sie sind? Listing 18.7: DoubleFloatEqual.java, main() double d1 = 0.02d; |
18.4.1 Behandlung des Überlaufs

Bei einigen mathematischen Fragestellungen müssen Sie feststellen können, ob Operationen wie die Addition, Subtraktion oder Multiplikation den Zahlenbereich sprengen, also etwa den Ganzzahlenbereich eines Integers von 32 Bit verlassen. Passt das Ergebnis einer Berechnung nicht in den Wertebereich einer Zahl, so wird dieser Fehler nicht von Java angezeigt; weder der Compiler noch die Laufzeitumgebung melden dieses Problem. Es gibt auch keine Ausnahme.
Mathematisch gilt a × a / a = a, also zum Beispiel 100 000 × 100 000 / 100 000 = 100 000. In Java ist das anders, da wir bei 100 000 × 100 000 einen Überlauf im int haben.
System.out.println( 100000 * 100000 / 100000 ); // 14100
liefert daher 14100. Wenn wir den Datentyp auf long erhöhen, indem wir hinter ein 100 000 ein L setzen, sind wir bei dieser Multiplikation noch sicher, da ein long das Ergebnis aufnehmen kann.
System.out.println( 100000L * 100000 / 100000 ); // 100000
Überlauf erkennen
Für die Operationen Addition und Subtraktion lässt sich das noch ohne allzu großen Aufwand implementieren. Wir vergleichen dazu zunächst das Ergebnis mit den Konstanten Integer.MAX_VALUE und Integer.MIN_VALUE. Natürlich muss der Vergleich so umgeformt werden, dass dabei kein Überlauf auftritt, also a + b > Integer.MAX_VALUE ist. Überschreiten die Werte diese maximalen Werte, ist die Operation nicht ohne Fehler möglich, und wir setzen das Flag canAdd auf false. Hier die Programmzeilen für die Addition:
if ( a >=0 && b >= 0 )
if ( ! (b <= Integer.MAX_VALUE – a) )
canAdd = false;
if ( a < 0 && b < 0 )
if ( ! (b >= Integer.MIN_VALUE – a) )
canAdd = false;
Bei der Multiplikation gibt es zwei Möglichkeiten: Zunächst einmal lässt sich die Multiplikation als Folge von Additionen darstellen. Dann ließe sich wiederum der Test mit der Konstanten Integer.XXX_VALUE durchführen. Diese Lösung scheidet jedoch wegen der Geschwindigkeit aus. Der andere Weg sieht eine Umwandlung nach long vor. Das Ergebnis wird zunächst als long berechnet und anschließend mit dem Ganzzahlwert vom Typ int verglichen.
Dies funktioniert jedoch nur mit Datentypen, die kleiner als long sind. long selbst fällt heraus, da es keinen Datentyp gibt, der größer ist. Mit ein wenig Rechenungenauigkeit würde ein double jedoch weiterhelfen, und bei präziserer Berechnung kann BigInteger helfen. Bei der Multiplikation im Wertebereich int lässt sich ähnlich wie bei der Addition auch b > Integer.MAX_VALUE / a schreiben. Bei b == Integer.MAX_VALUE / a muss ein Test genau zeigen, ob das Ergebnis in den Wertebereich passt.
Die eigene statische Methode canMulLong() soll bei der Frage nach dem Überlauf helfen:
Listing 18.8: Overflow.java
import java.math.BigInteger;
public class Overflow
{
private final static BigInteger MAX = BigInteger.valueOf( Long.MAX_VALUE );
public static boolean canMulLong( long a, long b )
{
BigInteger bigA = BigInteger.valueOf( a );
BigInteger bigB = BigInteger.valueOf( b );
return bigB.multiply( bigA ).compareTo( MAX ) <= 0;
}
public static void main( String[] args )
{
System.out.println( canMulLong(Long.MAX_VALUE/2, 2) ); // true
System.out.println( Long.MAX_VALUE/2 * 2 ); // 9223372036854775806
System.out.println( canMulLong(Long.MAX_VALUE/2 + 1, 2) ); // false
System.out.println( (Long.MAX_VALUE/2 + 1) * 2 ); //-9223372036854775808
}
}
Hinweis |
Wenn der Compiler beziehungsweise die JVM genau das macht, was wir uns wünschen, so würde auch ein return a * b / b == a; reichen. Doch eine JVM kann das zu return a == a; optimieren und somit zu return true; machen, sodass der Test nicht funktioniert. |
18.4.2 Was bitte macht ein ulp?
Die Math-Klasse bietet sehr spezielle Methoden, die für diejenigen interessant sind, die sich sehr genau mit Rechen(un)genauigkeiten beschäftigen und mit numerischen Näherungen arbeiten.
Der Abstand von einer Fließkommazahl zur nächsten ist durch den internen Aufbau nicht immer gleich. Wie groß genau der Abstand einer Zahl zur nächstmöglichen ist, zeigt ulp(double) beziehungsweise ulp(float). Der lustige Methodenname ist eine Abkürzung für Unit in the Last Place. Was genau denn die nächsthöhere/-niedrigere Zahl ist, ermitteln die Methoden nextUp(double)/nextUp(float), die auf nextAfter() zurückgreifen.
Beispiel |
Was kommt nach und vor 1: System.out.printf( "%.16f%n", Math.nextUp( 1 ) ); 1,0000001192092896 |
Dazu ein weiteres Beispiel: Je größer die Zahlen werden, desto größer werden auch die Sprünge.
Methode | Rückgabe des ulp |
Math.ulp( 0.00001 ) | 0,000000000000000000001694065895 |
Math.ulp( –1 ) | 0,00000011920928955078125 |
Math.ulp( 1 ) | 0,00000011920928955078125 |
Math.ulp( 2 ) | 0,0000002384185791015625 |
Math.ulp( 10E30 ) | 1125899906842624 |
Die üblichen mathematischen Fließkommaoperationen haben eine ulp von ½. Das ist so genau wie möglich. Um wie viel ulp die Math-Methoden vom echten Resultat abweichen können, steht in der JavaDoc. Rechenfehler lassen sich insbesondere bei komplexen Methoden nicht vermeiden. So darf sin() eine mögliche Ungenauigkeit von 1 ulp haben, atan2() von maximal 2 ulp und sinh(), chosh(), tanh() von 2,5 ulp.
Die ulp()-Methode ist für das Testen interessant, denn mit ihr lassen sich Abweichungen immer in der passenden Größenordnung realisieren. Bei kleinen Zahlen ergibt eine Differenz von vielleicht 0,001 einen Sinn, bei größeren Zahlen kann die Toleranz größer sein.
Java deklariert in den Klassen Double und Float drei besondere Konstanten. Sie lassen sich gut mit nextAfter() erklären. Am Beispiel von Double:
- MIN_VALUE = nextUp(0.0) = Double.longBitsToDouble(0x0010000000000000L)
- MIN_NORMAL = MIN_VALUE/(nextUp(1.0)-1.0) = Double.longBitsToDouble(0x1L)
- MAX_VALUE = nextAfter(POSITIVE_INFINITY, 0.0) =
Double.longBitsToDouble(0x7fefffffffffffffL)
Ihr Kommentar
Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen.