Im folgenden Programm können Sie sehen, dass jeder Wert etwas kleiner als .5
abgerundet ist, außer 0.5
.
for (int i = 10; i >= 0; i--) {
long l = Double.doubleToLongBits(i + 0.5);
double x;
do {
x = Double.longBitsToDouble(l);
System.out.println(x + " rounded is " + Math.round(x));
l--;
} while (Math.round(x) > i);
}
druckt
10.5 rounded is 11
10.499999999999998 rounded is 10
9.5 rounded is 10
9.499999999999998 rounded is 9
8.5 rounded is 9
8.499999999999998 rounded is 8
7.5 rounded is 8
7.499999999999999 rounded is 7
6.5 rounded is 7
6.499999999999999 rounded is 6
5.5 rounded is 6
5.499999999999999 rounded is 5
4.5 rounded is 5
4.499999999999999 rounded is 4
3.5 rounded is 4
3.4999999999999996 rounded is 3
2.5 rounded is 3
2.4999999999999996 rounded is 2
1.5 rounded is 2
1.4999999999999998 rounded is 1
0.5 rounded is 1
0.49999999999999994 rounded is 1
0.4999999999999999 rounded is 0
Ich verwende Java 6 Update 31.
java
floating-point
double
rounding
Peter Lawrey
quelle
quelle
0.5
die Zahl addiert und dann verwendet wirdfloor
. Java 7 dokumentiert es nicht mehr so (vermutlich / hoffentlich, weil sie es behoben haben).Antworten:
Zusammenfassung
In Java 6 (und vermutlich früher)
round(x)
ist als implementiertfloor(x+0.5)
. 1 Dies ist ein Spezifikationsfehler für genau diesen einen pathologischen Fall. 2 Java 7 schreibt diese fehlerhafte Implementierung nicht mehr vor. 3Das Problem
0,5 + 0,49999999999999994 ist genau 1 mit doppelter Genauigkeit:
Dies liegt daran, dass 0,49999999999999994 einen kleineren Exponenten als 0,5 hat. Wenn sie also hinzugefügt werden, wird ihre Mantisse verschoben und der ULP wird größer.
Die Lösung
Seit Java 7 implementiert OpenJDK (zum Beispiel) es folgendermaßen: 4
1. http://docs.oracle.com/javase/6/docs/api/java/lang/Math.html#round%28double%29
2. http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6430675 (Dank an @SimonNickerson für das Auffinden dieses Dokuments)
3. http://docs.oracle.com/javase/7/docs/api/java/lang/Math.html#round%28double%29
4. http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/7u40-b43/java/lang/Math.java#Math.round%28double%29
quelle
round
im Javadoc fürMath.round
oder in der Übersicht derMath
Klasse.Dies scheint ein bekannter Fehler zu sein ( Java-Fehler 6430675: Math.round hat ein überraschendes Verhalten für 0x1.fffffffffffffp-2 ), der in Java 7 behoben wurde.
quelle
Quellcode in JDK 6:
Quellcode in JDK 7:
Wenn der Wert in JDK 6 0,49999999999999994d ist, wird Floor aufgerufen und daher 1 zurückgegeben. In JDK 7
if
wird jedoch geprüft, ob die Zahl der größte Doppelwert unter 0,5 ist oder nicht. Da in diesem Fall die Zahl nicht der größte Doppelwert kleiner als 0,5 ist, gibt derelse
Block 0 zurück.Sie können 0.49999999999999999d ausprobieren, was 1, aber nicht 0 zurückgibt, da dies der größte Doppelwert unter 0,5 ist.
quelle
floor
entnehmen können, rundet die Methode sie korrekt ab.Ich habe das gleiche auf JDK 1.6 32-Bit, aber auf Java 7 64-Bit habe ich 0 für 0.49999999999999994, wobei gerundet 0 ist und die letzte Zeile nicht gedruckt wird. Es scheint ein VM-Problem zu sein. Bei Verwendung von Gleitkommazahlen sollten Sie jedoch erwarten, dass sich die Ergebnisse in verschiedenen Umgebungen (CPU-, 32- oder 64-Bit-Modus) geringfügig unterscheiden.
Und wenn
round
Matrizen usw. verwendet oder invertiert werden, können diese Bits einen großen Unterschied machen.x64-Ausgabe:
quelle
Die Antwort im Folgenden ist ein Auszug aus einem Oracle- Fehlerbericht 6430675 unter. Besuchen Sie den Bericht für die vollständige Erklärung.
Die Methoden {Math, StrictMath.round sind operativ definiert als
für doppelte Argumente. Während diese Definition normalerweise wie erwartet funktioniert, ergibt sie für 0x1.fffffffffffffp-2 (0.49999999999999994) das überraschende Ergebnis 1 anstelle von 0.
Der Wert 0,49999999999999994 ist der größte Gleitkommawert unter 0,5. Als hexadezimales Gleitkomma-Literal ist sein Wert 0x1.fffffffffffffp-2, was gleich (2 - 2 ^ 52) * 2 ^ -2 ist. == (0,5 - 2 ^ 54). Daher der genaue Wert der Summe
ist 1 - 2 ^ 54. Dies ist auf halbem Weg zwischen den beiden benachbarten Gleitkommazahlen (1 - 2 ^ 53) und 1. In der von Java verwendeten arithmetischen Rundung auf den nächsten geraden Rundungsmodus nach IEEE 754 ist der Wert der beiden, wenn ein Gleitkommaergebnis ungenau ist, umso näher Darstellbare Gleitkommawerte, die das genaue Ergebnis enthalten, müssen zurückgegeben werden. Wenn beide Werte gleich nahe beieinander liegen, wird derjenige zurückgegeben, dessen letztes Bit Null ist. In diesem Fall ist der korrekte Rückgabewert aus der Addition 1, nicht der größte Wert kleiner als 1.
Während die Methode wie definiert arbeitet, ist das Verhalten bei dieser Eingabe sehr überraschend. Die Spezifikation könnte dahingehend geändert werden, dass sie eher auf die nächste lange, runde Verbindung rundet, wodurch das Verhalten dieser Eingabe geändert werden kann.
quelle