Java BigDecimal - 需要解释

3
我曾经使用BigDecimal,但是对于两个不同的(在数学上相同的)表达式,我仍然得到了不同的结果:
第一个表达式:PI - (10^(-14)/PI)
第二个表达式:(PI^2 - 10^(-14))/PI
更简单地说,这就是方程式:Equation with PI
package set1;

import java.math.BigDecimal;
import java.math.RoundingMode;

public class FloatingLaws {
    final static BigDecimal PI = BigDecimal.valueOf(Math.PI);
    public static void main(String[] args) {        
        System.out.println(firstExpression());
        System.out.println(secondExpression());

    }

    private static BigDecimal secondExpression() {
        return PI.subtract((BigDecimal.valueOf(Math.pow(10, -14)).divide(PI,50,RoundingMode.CEILING)));

    }

    private static BigDecimal firstExpression() {
        return (PI.multiply(PI).subtract(BigDecimal.valueOf(Math.pow(10, -14)))).divide(PI, 50,RoundingMode.CEILING);
    }

}

执行此代码后,无论舍入的大小如何,最后一位数字始终不同。在我的情况下,我得到以下两个结果:
3.14159265358978981690113816209304300915191180404867
3.14159265358978981690113816209304300915191180404866

我的问题是为什么会发生这种情况,以及是否可以解决?

1
这真的让你很关心吗? - Idos
是的,它确实可以 :) - mrGreenBrown
6
我从未使用过 BigDecimal,但是我在你的代码中看到了很多“舍入模式”。在实际数学中(精度不受内存限制),在计算过程中进行四舍五入永远不是一个好主意,因为你得不到一个精确的答案。 - Arc676
正如@Arc676所提到的,试着在最后进行四舍五入! - Sercan Ozdemir
3个回答

5

这是因为您正在执行以下操作:

pi - ((10^-4)/pi)<- 仅对括号中的部分进行上舍入,

这与

((pi^2-10^-14)/pi)<- 整个表达式都被上舍入。

您使用BigDecimal和精度为50的CEILING舍入模式。在您的两个表达式中,当您除以PI数字时会应用上舍入。因此,如果您像第一个表达式中一样贪婪地除以PI,则可能会得到不太准确的结果-因为在您的公式完全执行之前,您就要CEIL中间值,所以您失去了由除以PI运算产生的上舍入部分,这在进一步计算中创建“错误”效果。 当您最后除以PI时,就像第二个表达式中一样,您使用更准确的公式,它只对结果进行上舍入,而不是像第一个表达式中那样中间值进行上舍入,因此计算更加精确,只对结果进行舍入,而不是中间值。


3
BigDecimal.subtract方法总是在不进行四舍五入的情况下产生两个BigDecimal数字之间的精确差值。另一方面,BigDecimal.divide通常会对结果进行四舍五入。在您的情况下,您使用的是CEILING舍入模式,它向上舍入(朝+无穷大方向)。当您计算 a-ceil(b/a)时,您实际上将整个结果向下舍入(假设已经舍入了a),而计算 ceil((a*a-b)/a) 时,您正在向上舍入。这就是为什么firstExpression()较大的原因。如果您使用HALF_EVEN舍入,则结果将相同。如果您使用FLOOR模式,则结果将相反。

此外,请查看BigDecimal.valueOf(Math.PI);

System.out.println(BigDecimal.valueOf(Math.PI));
> 3.141592653589793

实际的圆周率与所需的50位小数差距巨大。您应该明确地定义圆周率,如下所示:

final static BigDecimal PI = new BigDecimal("3.14159265358979323846264338327950288419716939937510");

现在的结果如下:
3.14159265358979005536378154537278750652190194908786
3.14159265358979005536378154537278750652190194908785

这与你的不同。

实际上,为了获得更好的结果,通常有意义使用比所需精度略高的精度,例如 desiredPrecision + 5,并且仅在最后一步进行四舍五入(最好使用 HALF_UP)。这样,您甚至可以计算出所需精度的余弦值(使用具有大量加法和除法的泰勒级数)。 - Rudy Velthuis

0

我修改了你的程序,尝试了Java所知道的所有舍入模式。 在Oracle JDK 8.72下运行,我发现在舍入模式HALF_UP、HALF_DOWN和HALF_EVEN下得到了相同的结果。但是Krzysztof是正确的,因为你没有在相同的位置进行舍入,错误注定会出现。

public class FloatingLaws {
    final static BigDecimal PI = BigDecimal.valueOf(Math.PI);

    public static void main(String[] args) {
        for (RoundingMode roundingMode : RoundingMode.values()) {
            System.out.println(roundingMode);
            System.out.println("Equal? "+firstExpression(roundingMode).equals(secondExpression(roundingMode)));
            System.out.println(firstExpression(roundingMode));
            System.out.println(secondExpression(roundingMode));
        }

    }

    private static BigDecimal secondExpression(RoundingMode roundingMode) {
    return PI.subtract((BigDecimal.valueOf(Math.pow(10, -14)).divide(PI, 50, roundingMode)));

    }

    private static BigDecimal firstExpression(RoundingMode roundingMode) {
    return (PI.multiply(PI).subtract(BigDecimal.valueOf(Math.pow(10, -14)))).divide(PI, 50, roundingMode);
    }

}

UP、DOWN、CEILING 和 FLOOR 常常存在较差的舍入误差,因为它们可以在最后一个保留数字上造成接近 1 的差异。HALF_UP、HALF_DOWN 和 HALF_EVEN 模式在最后一个保留数字上最多只会产生一半的差异,因此它们应该能够获得更好的结果。 - Patricia Shanahan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接