2012-03-29 168 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打印可能看起来令人惊讶的东西。