双精度混淆?

时间:2012-03-29 11:33:59

标签: java floating-point double

在回答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);

3 个答案:

答案 0 :(得分:2)

浮点类型无法准确表示所有实数。实际上,double是64位浮点类型,因此只能表示2个 64 个不同的值......并且存在无限数量的实数。 (实际上,0.49999999999999990d0.49999999999999999d之间存在无穷多的实数。)

您选择了一些数字,这些数字介于所有double值的连续值之间。换句话说,您已超出double类型的精度限制。

你能做些什么?那么获得更高精度的一种方法是使用BigDecimal类,它可以(在理论上)给你2个十进制数字的精度区域。缺点是你的代码会更复杂......而且速度要慢得多,这取决于你使用的精度。

另一种方法是认识到你可能不需要那么精确。

答案 1 :(得分: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会打印出看起来令人惊讶的内容。

答案 2 :(得分: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提供更多的尾数位。