我知道以下行为是一个老问题,但我仍然不理解。
System.out.println(0.1 + 0.1 + 0.1);
或者即使我使用BigDecimal
System.out.println(new BigDecimal(0.1).doubleValue()
+ new BigDecimal(0.1).doubleValue()
+ new BigDecimal(0.1).doubleValue());
为什么这个结果是:0.30000000000000004
而不是:0.3
?
我该如何解决这个问题?
您真正想要的是
new BigDecimal("0.1")
.add(new BigDecimal("0.1"))
.add(new BigDecimal("0.1"));
new BigDecimal(double)
构造函数获得了double
的所有不精确性,所以当您说0.1
时,您已经引入了舍入误差。使用String
构造函数避免了与通过double
相关联的舍入误差。
Firstnever,永远不要使用BigDecimal的双构造函数。在某些情况下这可能是正确的,但大多数情况下它不是
如果您可以控制您的输入,请使用BigDecimal字符串构造函数,正如已经提出的那样。这样你就能得到你想要的。如果您已经有了double(毕竟可能发生),请不要使用double构造函数,而是使用静态valueOf
方法。这有一个很好的优势,那就是我们得到了二重的cannonical表示,这至少缓解了问题。。并且结果通常更加直观。
这不是Java的问题,而是计算机的问题。核心问题在于从十进制格式(人类格式)到二进制格式(计算机格式)的转换。如果没有无限重复小数,一些十进制格式的数字就无法用二进制格式表示。
例如,0.3十进制是0.01001100…二进制。但计算机保存数字的"槽"(位)有限,因此无法保存所有的无限表示。它只保存0.01001100110011001100(例如)。但十进制中的数字不再是0.3,而是0.300000000000000004。
试试这个:
BigDecimal sum = new BigDecimal(0.1).add(new BigDecimal(0.1)).add(new BigDecimal(0.1));
编辑:事实上,仔细看一下Javadoc,它会遇到和原来一样的问题。构造函数BigDecimal(double)
将生成一个BigDecimal,它对应于0.1的精确浮点表示,而0.1并不完全等于0.1。
然而,这给出了确切的结果,因为整数总是可以精确地用浮点表示:
BigDecimal one = new BigDecimal(1);
BigDecimal oneTenth = one.divide(new BigDecimal(10));
BigDecimal sum = oneTenth.add(oneTenth).add(oneTenth);
问题是0.1用稍高的数字表示,例如
System.out.println(new BigDecimal(0.1));
打印
0.1000000000000000055511151231257827021181583404541015625
Double.toString()考虑了这个表示错误,所以你看不到它
类似地,0.3由一个略低于实际值的值表示
0.299999999999999988897769753748434595763683319091796875
如果你把0.1的代表值乘以3,你就不会得到0.3的代表值,相反,你会得到更高的
0.3000000000000000166533453693773481063544750213623046875
这不仅是表示误差,而且是由运算引起的舍入误差。这超过了Double.toString()将要校正的值,因此您可以看到舍入误差。
故事的寓意是,如果你使用float
或double
,也可以适当地完善解决方案。
double d = 0.1 + 0.1 + 0.1;
System.out.println(d);
double d2 = (long)(d * 1e6 + 0.5) / 1e6; // round to 6 decimal places.
System.out.println(d2);
打印
0.30000000000000004
0.3