执行涉及非终止 BigDecimals 的算术

Performing arithmetic involving non-terminating BigDecimals

除以 BigDecimal 时,结果可能是非终止的。因此,您必须提供 MathContext 或 RoundingMode/scale 作为除法运算的一部分。但是,根据算术运算的顺序,精度损失可能会导致差异。

如何在使用 BigDecimals 时避免由于精度损失(如下例所示)导致的差异?很想知道其他人是如何处理此类问题的。

示例:

BigDecimal v1 = new BigDecimal("29.14");
BigDecimal v2 = new BigDecimal("12");
BigDecimal v3 = new BigDecimal("75");
System.out.println(v1.divide(v2, MathContext.DECIMAL64).multiply(v3).setScale(2, RoundingMode.HALF_UP));
// Prints: 182.12
System.out.println(v1.multiply(v3).divide(v2, MathContext.DECIMAL64).setScale(2, RoundingMode.HALF_UP));
// Prints: 182.13

在上面的示例中,根据操作顺序,结果会发生变化。

在 运行 测试这个例子之前,我从来没有考虑过 BigDecimal 操作的顺序是一个问题。现在,我陷入了困境:)

这是浮点运算的一个常见问题,无论是二进制还是十进制:除法可能不准确。所以当你必须做一个乘法和一个除法时,如果你先做除法,结果不准确,当你以后做乘法时,你乘以第一个结果的误差。

在您的示例中,准确的结果是 182.125,它刚好在要四舍五入为 182.13 的边界内。先做除法,29.14/12 的精确结果是 2.428333...

DECIMAL64的16位四舍五入为2.428333333333333乘以75得到182.12499999999997四舍五入为182.12

那么这里可以做什么:

  • 如果没有溢出的风险,先做乘法,最后做除法。例如对于 a/b*c/d 你应该计算 (a*c)/(b*d)
  • 使用小数时尽量加一位精度数字。在这里你应该使用 3 个小数位的精度,因为初始数字是用 2 个 1 给出的。在这两种情况下,结果都是 182.125

但是无论如何,一旦您没有准确的结果,就有可能出现舍入误差,这是浮点计算所固有的。