Java BigDecimal-需要解释
我使用的是BigDecimal,但对于两个不同(数学上相同)的表达式,我仍然得到不同的结果: 第一个表达式:PI-(10^(-14)/PI) 第二个表达式:(PI^2-10^(-14))/PI 更简单地说,以下是方程式: 执行此代码后,无论舍入有多大,最后一位数字总是不同的。在我的例子中,我得到了以下两个结果: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
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);
}
}