2012-04-17 23 views
18

Dlaczego to prawda? Wydaje się, że Java daje wynik z niewielką rozbieżnością podczas mnożenia dwóch zmiennych w porównaniu z C, a nawet metodą Java Math.pow.Java i C zmiennoprzecinkowe: "x * x" różni się od "pow (x, 2)"?

Java:

float a = 0.88276923; 

double b = a * a; // b becomes 0.779281497001648 <---- what??? 
b = Math.pow(a,2); // b becomes 0.7792815081874238 

C:

float a = 0.88276923; 

double b = a * a; // b becomes 0.7792815081874238 
pow(a,2);   // b becomes 0.7792815081874238 

Aktualizacja: Per komentarzu Ed S., ja również, że zachowanie C zmienia się w zależności od kompilatora. Używanie gcc wydaje się pasować do zachowania Java. Korzystanie z wizualnego studio (w zależności od platformy docelowej) może produkować wyniki widziane powyżej lub te widoczne w Javie. Ugh.

+2

http://floating-point-gui.de/ –

+3

Ah, arytmetyka zmiennoprzecinkowa. Czysty bastion dokładności i niezawodności. – Perception

+1

Jestem świadomy, że zmienne nie są precyzyjne. Spodziewałbym się jednak, że ich nieprecyzyjność będzie konsekwentna. – mark

Odpowiedz

16

Jak PST i trutheality już mądrze zauważył, C promuje float do doubleprzed mnożenie. W rzeczywistości są one promowane do 80-bitowej rozszerzonej wartości precyzji, gdy są one popychane na stos. Oto wynik assembler (VS2005 x86 C89)

double b = a * a; 
00411397 fld   dword ptr [a] 
0041139A fmul  dword ptr [a] 
0041139D fstp  qword ptr [b] 

FLD Instruction

Dyspozycja FLD ładuje 32-bitowy, 64-bitowy lub 80-bitowy wartość zmiennoprzecinkową na stos. Ta instrukcja przekształca 32 i 64-bitowe operandy na 80-bitową rozszerzoną wartość precyzji przed przekazaniem wartości na stos zmiennoprzecinkowy.


Co ciekawe, jeśli budować kierować x64, instrukcja movss służy i masz wartość 0.779281497001648 w wyniku, to co widzisz w swoim przykładzie java. Spróbuj.

+2

+1. Teraz, gdzie w standardzie C (i nie zapomnij podać, który szkic!) Znajduje się instrukcja vauge, która może być interpretowana jako zachowanie zdefiniowane w implementacji na dwóch .... ;-) –

+2

@pst: Chciałbym mieć czas na patrzenie, ale .... jest noc z datą: D –

+0

@pst: Próbowałem go wyleczyć na x64 z ciekawości i widzisz zachowanie javy, ponieważ instrukcja 'fld' nie jest już używana (przynajmniej w mojej konfiguracji). –

10

Co Java dla

double b = a * a; 

jest pomnożyć a * a jako (32-bit) float pierwszy i konwertuje wynik (64-bit) double podczas przypisywania do b.

b = Math.pow(a,2); 

a konwertuje się do (64 bitów) double pierwsze (od parametrów Math.powdouble, double), a następnie kwadraty go.

Co jest zastanawiające (dla mnie) to dlaczego C wydaje się do oddania a „s do double pierwszy w

double b = a * a; 

Czy to jest w normie?

Edit: ja niejasno pamiętać o C nie wymaga szczególnej realizacji (w sensie ile bitów) wykorzystywane są do liczb ... jest to, że to, co się tu dzieje? Czy twoje 64-bitowe są float s? (W Javie float ma zawsze 32 bity, a double ma zawsze 64 bity).

Edit: Zarówno odpowiedź Ed S. i komentarz Marka, że ​​różne kompilatory dają różne wyniki wskazują, że wyniki są implementation- C i architecture- specyficzny.

+0

Zastanawiam się, czy istnieje również jakakolwiek ocena (optymalizacja) w czasie kompilacji? –

+0

(Chociaż wątpiłbym, że 'sizeof (float) == sizeof (double)' na nowoczesnej maszynie/kompilatorze ;-) –

+0

Na mojej maszynie są one promowane, gdy są wypychane na stos. Patrz: [Instrukcja FLD] (http://webster.cs.ucr.edu/AoA/Windows/HTML/RealArithmetica2.html) –