W poniższym programie widać, że każda wartość jest nieco mniejsza niż .5
zaokrąglona w dół, z wyjątkiem 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);
}
odciski
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
Używam aktualizacji Java 6 31.
java
floating-point
double
rounding
Peter Lawrey
źródło
źródło
0.5
do liczby, a następnie użyciefloor
; Java 7 nie dokumentuje go już w ten sposób (prawdopodobnie / mam nadzieję, że to naprawiły).Odpowiedzi:
Podsumowanie
W Javie 6 (i prawdopodobnie wcześniej)
round(x)
jest implementowany jakofloor(x+0.5)
. 1 To jest błąd specyfikacji, dokładnie dla tego jednego przypadku patologicznego. 2 Java 7 nie nakazuje już tej zepsutej implementacji. 3)Problem
0,5 + 0,49999999999999994 to dokładnie 1 z podwójną precyzją:
Wynika to z faktu, że 0,4999999999999999994 ma wykładnik mniejszy niż 0,5, więc gdy zostaną dodane, jego mantysa jest przesuwana, a ULP staje się większy.
Rozwiązanie
Od wersji Java 7 OpenJDK (na przykład) implementuje to w ten sposób: 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 (podziękowania dla @SimonNickerson za znalezienie tego)
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
źródło
round
w Javadoc dlaMath.round
ani w przeglądzieMath
klasy.Wygląda to na znany błąd (błąd Java 6430675: Math.round ma zaskakujące zachowanie dla 0x1.fffffffffffffp-2 ), który został naprawiony w Javie 7.
źródło
Kod źródłowy w JDK 6:
Kod źródłowy w JDK 7:
Gdy wartość wynosi 0,49999999999999994d, w JDK 6 wywoła floor, a zatem zwróci 1, ale w JDK 7
if
warunek sprawdza, czy liczba jest największą podwójną wartością mniejszą niż 0,5, czy nie. Podobnie jak w tym przypadku liczba nie jest największą podwójną wartością mniejszą niż 0,5, więcelse
blok zwraca 0.Możesz wypróbować 0,49999999999999999d, co zwróci 1, ale nie 0, ponieważ jest to największa podwójna wartość mniejsza niż 0,5.
źródło
floor
metoda zaokrągla je poprawnie.Mam to samo na 32-bitowym JDK 1.6, ale na 64-bitowym Java 7 mam 0 dla 0,49999999999999994, którego zaokrąglenie wynosi 0 i ostatni wiersz nie jest drukowany. Wydaje się, że jest to problem z maszyną wirtualną, jednak używając liczb zmiennoprzecinkowych, należy oczekiwać, że wyniki będą się nieco różnić w różnych środowiskach (procesor, tryb 32- lub 64-bitowy).
A podczas używania
round
lub odwracania macierzy itp. Te bity mogą mieć ogromną różnicę.wyjście x64:
źródło
Odpowiedź poniżej stanowi fragment raportu o błędach Oracle 6430675 pod adresem. Odwiedź raport, aby uzyskać pełne wyjaśnienie.
Metody {Math, StrictMath.round są zdefiniowane operacyjnie jako
dla podwójnych argumentów. Chociaż ta definicja zwykle działa zgodnie z oczekiwaniami, daje zaskakujący wynik 1 zamiast 0 dla 0x1.fffffffffffffp-2 (0,49999999999999994).
Wartość 0,49999999999999994 jest największą wartością zmiennoprzecinkową mniejszą niż 0,5. Jako szesnastkowy literał zmiennoprzecinkowy jego wartość wynosi 0x1.fffffffffffffp-2, co jest równe (2 - 2 ^ 52) * 2 ^ -2. == (0,5 - 2 ^ 54). Dlatego dokładna wartość sumy
wynosi 1 - 2 ^ 54. Znajduje się to w połowie odległości między dwiema sąsiednimi liczbami zmiennoprzecinkowymi (1–2 ^ 53) i 1. W rundzie arytmetycznej IEEE 754 do najbliższego parzystego trybu zaokrąglania używanego przez Javę, gdy wyniki zmiennoprzecinkowe są niedokładne, im bliżej dwóch reprezentatywne wartości zmiennoprzecinkowe, które zawierają dokładny wynik, muszą zostać zwrócone; jeśli obie wartości są jednakowo bliskie, zwracana jest ta, której ostatni bit zero. W takim przypadku poprawna wartość zwracana z dodania wynosi 1, a nie największa wartość mniejsza niż 1.
Podczas gdy metoda działa zgodnie z definicją, zachowanie na tych danych wejściowych jest bardzo zaskakujące; specyfikacja mogłaby zostać zmieniona na coś bardziej jak „Zaokrąglaj do najbliższego długiego, zaokrąglając więzi w górę”, co pozwoliłoby na zmianę zachowania na tym wejściu.
źródło