Java BigDecimal精度问题



我知道以下行为是一个老问题,但我仍然不理解。

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()将要校正的值,因此您可以看到舍入误差。

故事的寓意是,如果你使用floatdouble,也可以适当地完善解决方案。

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

最新更新