2012-03-29 124 views
1

在回答this問題,我得到了這些混亂的結果:雙精度混淆?

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 

爲什麼這種行爲顯示?

注:我只是通過打印d得到這些輸出;我的意思是我用過:

System.out.println(d); 
+2

[在java中保留精度與雙精度]的可能重複(http://stackoverflow.com/questions/322749/retain-precision-with-doubles-in-java) – 2012-03-29 11:41:17

回答

2

浮點類型不能準確表示所有實數。實際上,double是一個64位浮點類型,因此只能表示不同的值......並且有無數個實數。 (事實上​​,有0.49999999999999990d0.49999999999999999d之間的實數的無限數。)

您已選擇了一些數字,在設定的,所有double值連續值之間的下降。換句話說,你已經超過了double類型的精度極限。

你能做些什麼呢?那麼獲得更高精度的一種方法是使用BigDecimal類,它可以(理論上)給你在二十億分之十位精度的區域。缺點是你的代碼會更復雜...而且速度要慢得多,這取決於你使用多少精度。

另一種方法是認識到你可能不需要那麼高的精度。

0

只有某些數字可以完全表示爲doubles。有範圍三個這樣的數字在考慮:

  • 0.49999999999999990
  • 0.49999999999999994
  • 0.5

這些數字之間的一切都被四捨五入到最接近的三個。

如果你看看這些雙打是如何十六進制表示的,你會看到三個數字已經連續尾數(在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' 

代表數字,如0.49999999999999992究竟需要更多的尾數比double可以提供。

1

System.out.println(d)將經歷Double.toString這是一個相當複雜的方法(如其文檔中所見),並不總是像您期望的那樣行事。它基本上給出了唯一確定d的最短字符串。

也許這程序的輸出闡明此:

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])); 

輸出:

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      

如可以看到的,在文字有時也從它實際上代表的值遠,這意味着那Double.toString打印可能看起來令人驚訝的東西。