Java BigDecimal - 需要解释
Java BigDecimal - explanation needed
我使用的是 BigDecimal,但对于两个不同的(数学上相同的)表达式,我仍然得到不同的结果:
第一个表达式:PI - (10^(-14)/PI)
第二个表达式:(PI^2 - 10^(-14))/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
我的问题是为什么会发生这种情况,是否可以解决?
那是因为你这样做:
pi - ((10^-4)/pi)
<- 仅括号内部分为顶,
不同于
((pi^2-10^-14)/pi)
<- 整个表达式是上限。
您使用 BigDecimal 并且您使用精度为 50 的舍入模式 CEILING。在您的两个表达式中,当您除以 PI 编号时应用上限。因此,如果你像第一个表达式一样急切地除以 PI,那么你可能会得到不太准确的结果——因为你是 CEIL 中间值,在你的公式完全执行之前,所以你从除以 PI 操作中松开了 CEILED 部分,这在进一步的计算中创建"error" 效果。
当你最后除以 PI 时,就像在第二个表达式中,你使用更准确的公式,它只是结果的上限,而不是像第一个表达式中的中间值,所以它计算更精确,只舍入结果而不是中间值。
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
它甚至不接近实际的 PI 编号(考虑到您需要 50 位数字)。您应该像这样明确定义 PI:
final static BigDecimal PI = new BigDecimal("3.14159265358979323846264338327950288419716939937510");
现在的结果如下:
3.14159265358979005536378154537278750652190194908786
3.14159265358979005536378154537278750652190194908785
这和你的完全不同。
我修改了您的程序以尝试所有舍入模式 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);
}
}
我使用的是 BigDecimal,但对于两个不同的(数学上相同的)表达式,我仍然得到不同的结果:
第一个表达式:PI - (10^(-14)/PI)
第二个表达式:(PI^2 - 10^(-14))/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
我的问题是为什么会发生这种情况,是否可以解决?
那是因为你这样做:
pi - ((10^-4)/pi)
<- 仅括号内部分为顶,
不同于
((pi^2-10^-14)/pi)
<- 整个表达式是上限。
您使用 BigDecimal 并且您使用精度为 50 的舍入模式 CEILING。在您的两个表达式中,当您除以 PI 编号时应用上限。因此,如果你像第一个表达式一样急切地除以 PI,那么你可能会得到不太准确的结果——因为你是 CEIL 中间值,在你的公式完全执行之前,所以你从除以 PI 操作中松开了 CEILED 部分,这在进一步的计算中创建"error" 效果。 当你最后除以 PI 时,就像在第二个表达式中,你使用更准确的公式,它只是结果的上限,而不是像第一个表达式中的中间值,所以它计算更精确,只舍入结果而不是中间值。
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
它甚至不接近实际的 PI 编号(考虑到您需要 50 位数字)。您应该像这样明确定义 PI:
final static BigDecimal PI = new BigDecimal("3.14159265358979323846264338327950288419716939937510");
现在的结果如下:
3.14159265358979005536378154537278750652190194908786
3.14159265358979005536378154537278750652190194908785
这和你的完全不同。
我修改了您的程序以尝试所有舍入模式 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);
}
}