Java BigDecimal-需要解释

Java BigDecimal-需要解释,java,floating-point,bigdecimal,floating-accuracy,Java,Floating Point,Bigdecimal,Floating Accuracy,我使用的是BigDecimal,但对于两个不同(数学上相同)的表达式,我仍然得到不同的结果: 第一个表达式:PI-(10^(-14)/PI) 第二个表达式:(PI^2-10^(-14))/PI 更简单地说,以下是方程式: 执行此代码后,无论舍入有多大,最后一位数字总是不同的。在我的例子中,我得到了以下两个结果: 3.14159265358978981690113816209304300915191180404867 3.14159265358978981690113816209304300915

我使用的是BigDecimal,但对于两个不同(数学上相同)的表达式,我仍然得到不同的结果:

第一个表达式:PI-(10^(-14)/PI)

第二个表达式:(PI^2-10^(-14))/PI

更简单地说,以下是方程式:

执行此代码后,无论舍入有多大,最后一位数字总是不同的。在我的例子中,我得到了以下两个结果:

3.14159265358978981690113816209304300915191180404867
3.14159265358978981690113816209304300915191180404866

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

这是因为你正在这样做:


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

还要看看什么是
BigDecimal.valueOf(Math.PI)

它甚至不接近实际的PI数(考虑到您需要50位数字的事实)。您应该明确定义PI,如下所示:

final static BigDecimal PI = new BigDecimal("3.14159265358979323846264338327950288419716939937510");
现在,结果如下:

3.14159265358979005536378154537278750652190194908786
3.14159265358979005536378154537278750652190194908785

这与您的程序大不相同。

我修改了您的程序以尝试java知道的所有舍入模式。 在OracleJDK8.72下运行,对于舍入模式,我得到了相同的结果,即半向上、半向下和半偶数。但是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);
    }

}

这真的让你如此担心吗?是的,确实如此:)我从来没有使用过
BigDecimal
,但我在你的代码中看到了很多“舍入模式”。在计算中间舍入从来都不是一个好主意(在实际的数学中,精度不受内存限制),因为你不会得到精确的答案。“上”、“下”、“天花板”和“地板”的舍入误差往往更大,因为它们在最后一个保留的数字中可能相差近1。半向上、半向下和半向偶数模式在最后保留的数字中最多相差一半,因此它们应该会得到更好的结果。实际上,为了得到更好的结果,通常需要使用稍高于您需要的精度,例如
desiredPrecision+5
,并且只需要四舍五入(最好是
半向上
)最后。这样,您甚至可以计算余弦(使用泰勒级数进行大量的加法和除法)以达到所需的精度。
3.14159265358979005536378154537278750652190194908786
3.14159265358979005536378154537278750652190194908785
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);
    }

}