我使用的是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
我的问题是为什么会发生这种情况并且可以解决?
答案 0 :(得分:5)
这是因为你这样做:
pi - ((10^-4)/pi)
< - 只有括号中的部分被剔除,
与
不同 ((pi^2-10^-14)/pi)
< - 整个表达都被玷污了。
您使用BigDecimal并且您具有精确度为50的舍入模式CEILING。在两个表达式中,当您除以PI编号时应用上限。因此,如果您像第一个表达式中那样切换PI,那么您可能会得到不太准确的结果 - 因为您的CEIL中间值,在您的公式完全执行之前,所以您通过PI操作将CEILED部分从中断,这在进一步的计算中创建“错误”效应。 当你最后除以PI时,就像在第二个表达式中一样,你使用更精确的公式,它只是结果的上限,而不是像第一个表达式中的中间值,所以它计算得更精确,只舍入结果而不是中间值。
答案 1 :(得分: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
它甚至没有接近实际的PI编号(假设您需要50位数字)。您应该明确定义PI:
final static BigDecimal PI = new BigDecimal("3.14159265358979323846264338327950288419716939937510");
现在结果如下:
3.14159265358979005536378154537278750652190194908786
3.14159265358979005536378154537278750652190194908785
这与你的完全不同。
答案 2 :(得分: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);
}
}