代码之家  ›  专栏  ›  技术社区  ›  angrycrab

执行涉及非终止大小数的算术

  •  0
  • angrycrab  · 技术社区  · 8 年前

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

    在使用大小数时,如何避免由于精度损失(如下图所示)而导致的差异?很想知道其他人是如何处理这类问题的。

    例子:

    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
    

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

    在测试时遇到这个示例之前,我从来没有想过bigdecim操作的顺序是一个问题。现在,我左右为难:)

    1 回复  |  直到 8 年前
        1
  •  1
  •   Serge Ballesta    8 年前

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

    在您的示例中,精确结果为182.125,正好在要四舍五入到182.13的边界处。首先进行除法时,29.14/12的精确结果是2.428333。。。

    对于16位小数64,四舍五入为2.428333333333333,再乘以75,得到182.12499999997,四舍五入为182.12

    那么,我们可以在这里做些什么:

    • 如果没有溢出风险,则首先进行乘法运算,最后进行除法运算。例如 a/b*c/d 你应该计算 (a*c)/(b*d)
    • 使用小数时,请尝试添加一个精确数字。在这里,您应该使用3位十进制数字的精度,因为初始数字是用2个1给出的。两种情况下的结果都是182.125

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