2012-07-16 14 views
5

Powiel możliwe:
Retain precision with Doubles in javaJava podwójnej precyzji ze stałym mnożenie/dzielenie

Znasz różnicę między tymi dwoma procesami w Javie.

final double m1 = 11d/1e9; // gives 1.1e-8 
final double m2 = 11d * 1e-9; // gives 1.1000000000000001e-8 

Widzę w wygenerowanym kod bajtowy, że prekompilowany wynik m2 już nie jest tym, czego się spodziewałem.
W wyjściu javap -verbose -c widzę następującą wartość:

const #3 = double 1.1E-8d; 
[...] 
const #6 = double 1.1000000000000001E-8d; 

Gdy używam m1 lub m2 w innych wyrażeń, nie mam ten sam rezultat.

Kiedy próbuję to samo w C, M1 i M2 jest ściśle 1.1E-8

Myślę, że mój problem jest w sposób java obsługuje podwójnej precyzji obliczeń, ale nie mogę wytłumaczyć co tęsknię.

Odpowiedz

1

The Różnica polega na tym, że 1e9 jest dokładnie reprezentowalna, a 1e-9 nie jest dokładnie reprezentowalna. Mały błąd reprezentacji powoduje błąd arytmetyczny, który można zobaczyć.

System.out.println(new BigDecimal(1e9)); 
System.out.println(new BigDecimal(1e-9)); 

drukuje

1000000000 
1.0000000000000000622815914577798564188970686927859787829220294952392578125E-9 

Różnica nie jest to, czy użyłeś * lub / ale że użyłeś cały numer w jednym przypadku i frakcję w drugiej.

System.out.println(11 * 1e9); 
System.out.println(11 * 1e-9); 

drukuje

1.1E10 
1.1000000000000001E-8 
+0

OK, to jest wyjaśnienie dla Javy, ale teraz dlaczego po uruchomieniu tego samego kodu w C mam całą precyzję, której potrzebowałem? – Joker

+0

Kompilator C zoptymalizuje kod * znacznie * dalej niż "javac". Może to oznaczać, że może przetłumaczyć '/ 1e-9' na' * 1e9', ponieważ a) jest szybszy i b) dokładniejszy.Kompilator Java ma bardzo mało optymalizacji, pozostawia to JIT, co oznacza również, że zoptymalizowany i zoptymalizowany kod musi zrobić to samo, w przeciwnym razie zobaczyłbyś zmianę zachowania podczas działania programu. Konwersja "podwójnego" do łańcucha jest również inna w językach C i Java, ale mniej prawdopodobne jest, że będzie przyczyną tej różnicy. W obu przypadkach używają tej samej jednostki FPU. –

+1

Również kompilator języka C może skorzystać z 80-bitowych rejestrów zmiennoprzecinkowych/obliczeń, które posiada komputer. Jest to zależne od platformy i jest dostępne tylko w procesorach x86/x64. Java będzie zachowywać się tak samo na każdej platformie, więc użyje tylko 64-bitowego zmiennoprzecinkowego, nawet jeśli dostępny jest 80-bitowy zmiennoprzecinkowy. –

3

Nie ma różnicy między Javą i C, obie są zgodne ze standardem zmiennoprzecinkowym IEEE. Jedyną różnicą może być sposób drukowania. Dokładna liczba 1.1e-8 nie może być reprezentowana jako double. Różnicę w wynikach można przypisać szczegółom, w jaki sposób błąd reprezentacji został zgromadzony, i do jakiego zaokrąglenia używa się do wydrukowania wyniku.

+0

tak, ale dzieli od 1 do 10^9 – Pooya

+0

myślę, że różnica jest tylko w tym, jak wiele błędów zostało zgromadzone, gdzie druga linia zaledwie kilka kroków na progu, który zostanie wydrukowany. –

0

Jeśli używasz BigDecimal jak poniżej:

BigDecimal a=new BigDecimal(1e-9); 
BigDecimal b=new BigDecimal(11); 
System.out.println(a.multiply(b).toString()); 

widać, że numer jest

1.10000000000000006850975060355784206078677556206457666121423244476318359375E-8 

JVM zaokrągla w górę swój numer i zmienić go na adres:

1.1000000000000001e-8 
Powiązane problemy