592 Stimmen

Warum ergibt Math.round(0.49999999999999994) 1?

Im folgenden Programm können Sie sehen, dass jeder Wert etwas kleiner als .5 wird abgerundet, außer bei 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.

594voto

Oliver Charlesworth Punkte 259497

Zusammenfassung

In Java 6 (und vermutlich früher), round(x) ist implementiert als floor(x+0.5) . 1 Dies ist ein Spezifikationsfehler, der genau diesen einen pathologischen Fall betrifft. 2 Java 7 schreibt diese fehlerhafte Implementierung nicht mehr vor. 3

Das Problem

0,5+0,499999999999994 ist genau 1 in doppelter Genauigkeit:

static void print(double d) {
    System.out.printf("%016x\n", Double.doubleToLongBits(d));
}

public static void main(String args[]) {
    double a = 0.5;
    double b = 0.49999999999999994;

    print(a);      // 3fe0000000000000
    print(b);      // 3fdfffffffffffff
    print(a+b);    // 3ff0000000000000
    print(1.0);    // 3ff0000000000000
}

Der Grund dafür ist, dass 0,499999999999994 einen kleineren Exponenten als 0,5 hat, so dass bei der Addition die Mantisse verschoben wird und der ULP größer wird.

Die Lösung

Seit Java 7 ist dies beispielsweise in OpenJDK so implementiert: 4

public static long round(double a) {
    if (a != 0x1.fffffffffffffp-2) // greatest double value less than 0.5
        return (long)floor(a + 0.5d);
    else
        return 0;
}

1. <a href="http://docs.oracle.com/javase/6/docs/api/java/lang/Math.html#round%28double%29" rel="noreferrer">http://docs.oracle.com/javase/6/docs/api/java/lang/Math.html#round%28double%29</a>

2. <a href="http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6430675" rel="noreferrer">http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6430675 </a>(Dank an @SimonNickerson für die Entdeckung dieses Artikels)

3. <a href="http://docs.oracle.com/javase/7/docs/api/java/lang/Math.html#round%28double%29" rel="noreferrer">http://docs.oracle.com/javase/7/docs/api/java/lang/Math.html#round%28double%29</a>

4. <a href="http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/7u40-b43/java/lang/Math.java#Math.round%28double%29" rel="noreferrer">http://grepcode.com/file/repository.grepcode.com/java/Root/jdk/openjdk/7u40-b43/java/lang/Math.java#Math.round%28double%29</a>

241voto

Simon Nickerson Punkte 40349

Dies scheint ein bekannter Fehler zu sein ( Java-Fehler 6430675: Math.round hat überraschendes Verhalten für 0x1.fffffffffffffp-2 ), die in Java 7 behoben wurde.

87voto

Chandra Sekhar Punkte 17730

Quellcode in JDK 6:

public static long round(double a) {
    return (long)Math.floor(a + 0.5d);
}

Quellcode in JDK 7:

public static long round(double a) {
    if (a != 0x1.fffffffffffffp-2) {
        // a is not the greatest double value less than 0.5
        return (long)Math.floor(a + 0.5d);
    } else {
        return 0;
    }
}

Wenn der Wert 0,49999999999999994d ist, wird in JDK 6 der Aufruf Boden und gibt daher 1 zurück, aber in JDK 7 ist die if Bedingung prüft, ob die Zahl der größte Double-Wert kleiner als 0,5 ist oder nicht. Da in diesem Fall die Zahl nicht der größte Double-Wert kleiner als 0,5 ist, ist die else Block gibt 0 zurück.

Sie können es mit 0.499999999999999d versuchen, was 1 zurückgibt, aber nicht 0, da dies der größte Double-Wert kleiner als 0.5 ist.

28voto

Danubian Sailor Punkte 21985

Ich habe das gleiche auf JDK 1.6 32-Bit, aber auf Java 7 64-Bit habe ich 0 für 0,4999999999999999994, die gerundet 0 ist und die letzte Zeile wird nicht gedruckt. Es scheint ein VM-Problem zu sein, aber bei der Verwendung von Fließkommazahlen sollte man erwarten, dass die Ergebnisse in verschiedenen Umgebungen (CPU, 32- oder 64-Bit-Modus) ein wenig abweichen.

Und bei der Verwendung von round oder Invertierung von Matrizen usw., diese Bits kann einen großen Unterschied machen.

x64-Ausgabe:

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 0

15voto

shiv.mymail Punkte 1219

Die folgende Antwort ist ein Auszug aus einer Oracle Fehlerbericht 6430675 bei. Besuchen Sie den Bericht, um die vollständige Erklärung zu erhalten.

Die Methoden {Math, StrictMath.round sind operationell definiert als

(long)Math.floor(a + 0.5d)

für doppelte Argumente. Während diese Definition normalerweise wie erwartet funktioniert, ergibt sie für 0x1.fffffffffffffffp-2 (0,49999999999999994) das überraschende Ergebnis 1 statt 0.

Der Wert 0,499999999999994 ist der größte Gleitkommawert, der kleiner als 0,5 ist. Als hexadezimales Fließkomma-Literal ist sein Wert 0x1.fffffffffffffp-2, was gleich (2 - 2^52) * 2^-2 == (0,5 - 2^54) ist. Daher ist der genaue Wert der Summe

(0.5 - 2^54) + 0.5

ist 1 - 2^54. Dies liegt in der Mitte zwischen den beiden benachbarten Fließkommazahlen (1 - 2^53) und 1. In der von Java verwendeten IEEE-754-Arithmetik wird auf die nächste gerade Zahl gerundet. Wenn ein Fließkomma-Ergebnis ungenau ist, muss derjenige der beiden darstellbaren Fließkomma-Werte zurückgegeben werden, der näher am exakten Ergebnis liegt; wenn beide Werte gleich nahe beieinander liegen, wird der Wert zurückgegeben, dessen letztes Bit Null ist. In diesem Fall ist der korrekte Rückgabewert der Addition 1, nicht der größte Wert kleiner als 1.

Während die Methode wie definiert funktioniert, ist das Verhalten bei dieser Eingabe sehr überraschend; die Spezifikation könnte in etwas wie "Runden auf die nächste Länge, Aufrunden von Gleichständen" geändert werden, wodurch das Verhalten bei dieser Eingabe geändert werden könnte.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X