2012-03-29 2 views
1

Während this Frage zu beantworten, habe ich diese verwirrende Ergebnisse:Doppelpräzisionsverwechslung?

double d = 0.49999999999999990d; //output 0.4999999999999999 as expected 
d = 0.49999999999999991d; //output 0.4999999999999999 
d = 0.49999999999999992d; //output 0.49999999999999994 
d = 0.49999999999999993d; //output 0.49999999999999994 
d = 0.49999999999999994d; //output 0.49999999999999994 as expected 
d = 0.49999999999999995d; //output 0.49999999999999994 
d = 0.49999999999999996d; //output 0.49999999999999994 
d = 0.49999999999999997d; //output 0.49999999999999994 
d = 0.49999999999999998d; //output 0.5 

Warum dieses Verhalten zeigt, ist?

HINWEIS: Ich habe diese Ausgaben nur durch Drucken von d; Ich meine ich benutzte:

System.out.println(d); 
+2

mögliche Duplikate von [Präzision mit Doppel in Java beibehalten] (http://stackoverflow.com/questions/322749/retain-precision-with-doubles-in-java) –

Antwort

2

Fließkommatypen können nicht genau alle reellen Zahlen darstellen. Tatsächlich ist ein double ein 64-Bit Gleitkommatyp und kann daher nur 2 verschiedene Werte darstellen ... und es gibt eine unendliche Anzahl von reellen Zahlen. (In der Tat gibt es eine unendliche Anzahl von reellen Zahlen zwischen 0.49999999999999990d und 0.49999999999999999d.)

Sie haben einige Zahlen ausgewählt, die in den Set-of-alle double Werten zwischen aufeinanderfolgenden Werten fallen. Mit anderen Worten, Sie haben die Genauigkeitsgrenzen für den Typ double überschritten.

Was können Sie dagegen tun? Nun, ein Weg, um mehr Präzision zu erhalten, ist die Verwendung der BigDecimal Klasse, die (theoretisch) Ihnen in der Region von 2 Milliarden Dezimalstellen Genauigkeit geben kann. Der Nachteil ist, dass Ihr Code komplizierter wird ... und deutlich langsamer, je nachdem wie viel Präzision Sie verwenden.

Der andere Ansatz ist zu erkennen, dass Sie wahrscheinlich nicht so viel Präzision benötigen.

0

Nur bestimmte Nummern können genau wie doubles dargestellt werden. Es gibt drei solche Zahlen im Bereich unter Berücksichtigung:

  • 0.49999999999999990
  • 0.49999999999999994
  • 0.5

Alles zwischen diesen Zahlen auf den nächsten der drei abgerundet wird.

Wenn man sich anschaut, wie diese Doppel in hex vertreten sind, sehen Sie, dass die drei Zahlen in Folge Mantissen haben (den Teil vor dem p):

In [20]: float.hex(0.49999999999999990) 
Out[20]: '0x1.ffffffffffffep-2' 

In [21]: float.hex(0.49999999999999994) 
Out[21]: '0x1.fffffffffffffp-2' 

In [22]: float.hex(0.5) 
Out[22]: '0x1.0000000000000p-1' 

Darstellung von Zahlen wie 0.49999999999999992 genau erfordern würde, mehr Bits von Mantisse als double können anbieten.

1

System.out.println(d) wird durch Double.toString gehen, die eine ziemlich komplexe Methode ist (wie in der Dokumentation zu sehen) und wird nicht immer so verhalten, wie Sie es erwarten würden. Es gibt grundsätzlich die kürzeste Zeichenkette, die d eindeutig bestimmt.

Vielleicht ist die Ausgabe dieses Programms verdeutlicht dies:

double[] tests = { 
     0.49999999999999990d, //output 0.4999999999999999 as expected 
     0.49999999999999991d, //output 0.4999999999999999 
     0.49999999999999992d, //output 0.49999999999999994 
     0.49999999999999993d, //output 0.49999999999999994 
     0.49999999999999994d, //output 0.49999999999999994 as expected 
     0.49999999999999995d, //output 0.49999999999999994 
     0.49999999999999996d, //output 0.49999999999999994 
     0.49999999999999997d, //output 0.49999999999999994 
     0.49999999999999998d, //output 0.5 
    }; 

String[] literals = { 
     "0.49999999999999990d", 
     "0.49999999999999991d", 
     "0.49999999999999992d", 
     "0.49999999999999993d", 
     "0.49999999999999994d", 
     "0.49999999999999995d", 
     "0.49999999999999996d", 
     "0.49999999999999997d", 
     "0.49999999999999998d", 
    }; 

String f = "%-25s%-65s%-25s%n"; 
System.out.printf(f, "Literal", "Actually represents", "Printed as"); 

for (int i = 0; i < tests.length; i++) 
    System.out.printf(f, literals[i], 
         new BigDecimal(tests[i]).toString(), 
         Double.valueOf(tests[i])); 

Ausgang:

Literal     Actually represents            Printed as    
0.49999999999999990d  0.49999999999999988897769753748434595763683319091796875   0.4999999999999999  
0.49999999999999991d  0.49999999999999988897769753748434595763683319091796875   0.4999999999999999  
0.49999999999999992d  0.499999999999999944488848768742172978818416595458984375   0.49999999999999994  
0.49999999999999993d  0.499999999999999944488848768742172978818416595458984375   0.49999999999999994  
0.49999999999999994d  0.499999999999999944488848768742172978818416595458984375   0.49999999999999994  
0.49999999999999995d  0.499999999999999944488848768742172978818416595458984375   0.49999999999999994  
0.49999999999999996d  0.499999999999999944488848768742172978818416595458984375   0.49999999999999994  
0.49999999999999997d  0.499999999999999944488848768742172978818416595458984375   0.49999999999999994  
0.49999999999999998d  0.5                0.5      

Wie man sehen kann, ist die wörtliche manchmal weit von dem Wert, den es tatsächlich darstellt, was bedeutet, dass Double.toString etwas druckt, das vielleicht überraschend aussieht.