在回答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);
答案 0 :(得分:2)
浮点类型无法准确表示所有实数。实际上,double
是64位浮点类型,因此只能表示2个 64 个不同的值......并且存在无限数量的实数。 (实际上,0.49999999999999990d
和0.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
提供更多的尾数位。