2012-10-26 17 views
7

Chcę zrozumieć, jak typ Java double będzie przechowywać jego wartość w pamięci w Javie. Kiedy uruchomić następujący kod uzyskać nieoczekiwany wynik:Nieoczekiwane zachowanie danych podwójnego typu prymitywnego

public static void main(String[] args) { 

    float a = 1.5f; 
    float b= 0.5f; 
    double c= 1.5; 
    double d = 0.5; 

    float a1 = 1.4f; 
    float b1= 0.5f; 
    double c1= 1.4; 
    double d1 = 0.5; 

    System.out.println(" a- b is " + (a-b)); 
    System.out.println(" c- d is " + (c-d)); 
    System.out.println("a1-b1 is " + (a1-b1)); 
    System.out.println("c1-d1 is " + (c1-d1)); 

}

wyjściowa:

 
a- b is 1.0 
c- d is 1.0 
a1-b1 is 0.9 
c1-d1 is 0.8999999999999999 

Dlaczego c1-d1 nie równa 0.9?

Próbowałem również inne wartości, ale w pewnym momencie zwracają oczekiwany wynik, a czasem nie.

+4

podobne do http://stackoverflow.com/questions/322749/retain-precision-with-doubles-in-java – tjg184

+1

To może pomóc http: //epramono.blogspot .com/2005/01/double-vs-bigdecimal.html – kosa

+0

Zobacz także http://mindprod.com/jgloss/floatingpoint.html –

Odpowiedz

3

Chociaż prawdopodobnie słyszałeś o błędach zaokrąglania, możesz się zastanawiać, dlaczego masz tutaj błąd zaokrąglenia.

float a1 = 1.4f; 
float b1 = 0.5f; 
double c1 = 1.4; 
double d1 = 0.5; 

System.out.println(new BigDecimal(a1) + " - " + new BigDecimal(b1) + " is " + 
     new BigDecimal(a1).subtract(new BigDecimal(b1)) + " or as a float is " + (a1 - b1)); 
System.out.println(new BigDecimal(c1) + " - " + new BigDecimal(d1) + " is " + 
     new BigDecimal(c1).subtract(new BigDecimal(d1)) + " or as a double is " + (c1 - d1)); 

drukuje

1.39999997615814208984375 - 0.5 is 0.89999997615814208984375 or as a float is 0.9 
1.399999999999999911182158029987476766109466552734375 - 0.5 is 
    0.899999999999999911182158029987476766109466552734375 
    or as a double is 0.8999999999999999 

Jak widać, ani float ani double może reprezentować dokładnie te wartości, a gdy pływak lub podwójny jest drukowany, niektóre zaokrąglania występuje ukryć tego od ciebie. W tym przypadku zmiennoprzecinkowe zaokrąglenie do 7 miejsc dziesiętnych daje oczekiwaną liczbę. W przypadku liczby podwójnej, która ma 16 cyfr precyzji, błąd zaokrąglenia jest widoczny.

Jako @Eric Postpischil, odnotowuje, czy operacja float lub double ma błąd zaokrąglenia, zależy całkowicie od użytych wartości. W tej sytuacji płynność wydawała się być bardziej dokładna, nawet jeśli reprezentowana wartość była większa od wartości 0,9, a nie podwójnej.

W skrócie: jeśli zamierzasz używać float lub double, powinieneś użyć rozsądnej strategii zaokrąglania. Jeśli nie możesz tego zrobić, użyj BigDecimal.

System.out.printf("a1 - b1 is %.2f%n", (a1 - b1)); 
System.out.printf("c1 - d1 is %.2f%n", (c1 - d1)); 

drukuje

a1 - b1 is 0.90 
c1 - d1 is 0.90 

Podczas drukowania float lub double, to zakłada się, że najbliższa krótki wartość dziesiętną jest jeden naprawdę chcesz. tj. w obrębie 0,5 ulp.

E.g.

double d = 1.4 - 0.5; 
float f = d; 
System.out.println("d = " + d + " f = " + f); 

drukuje

d = 0.8999999999999999 f = 0.9 
+2

Zdanie "W przypadku podwójnego, który ma 16 cyfr precyzji, błąd zaokrąglenia jest widoczny" sugeruje, że podwójna wartość została wyświetlona z większą liczbą cyfr, ponieważ podwójność ma większą precyzję. Tak nie jest. Dla każdego typu, zmiennoprzecinkowe i podwójne, niezależnie od tego, czy wynikiem odejmowania .5 jest również reprezentowalna wartość najbliższa dokładnemu wynikowi (gdy używane są oryginalne cyfry), jest to efektywne zjawisko. Jest to funkcja tego, co dzieje się z bitami w miejscu, w którym występuje zaokrąglenie, a nie funkcją liczby bitów. Na przykład spróbuj "1.2f - .5f" i "1.2 - .5". –

+0

@EricPostpischil Dziękuję. Jak napisano, nie było jasne, co mam na myśli. –

1

Dokumentacja Java dla println odnosi się (poprzez kilka linków) do dokumentacji dla toString. documentation for toString mówi, że liczba cyfr drukowanych dla float lub double jest liczbą potrzebną do jednoznacznego odróżnienia wartości od sąsiadujących reprezentowalnych wartości w tym samym typie.

Gdy "1.4f" jest konwertowane na zmienną, wynikiem jest 1.39999997615814208984375 (w szesnastkowym zmiennoprzecinkowym, 0x1.666666p + 0). Po odjęciu wartości 5, wynikiem jest 0.89999997615814208984375 (0x1.ccccccp-1). Tak się składa, że ​​ten float jest również floatem, który jest najbliższy .9. Tak więc po wydrukowaniu drukowany jest ".9".

Gdy "1,4" zostanie przekonwertowane na podwójne, wynikiem jest 1,399999999999999911182158029987476766109466552734375 (0x1.6666666666666p + 0). Po odjęciu wartości 5, wynikiem jest 0.899999999999999911182158029987476766109466552734375 (0x1.cccccccccccccp-1). To jest nie podwójne, które jest najbliższe .9, ponieważ 0.90000000000000002220446049250313080847263336181640625 (0x1.cccccccccccp1p-1) jest bliżej. Dlatego po wydrukowaniu specyfikacja Java wymaga dokładniejszego wydrukowania wartości, odróżniając ją od .9. Wynik "0.8999999999999999", dokładnie przedstawia rzeczywistą wartość.

Powiązane problemy