BigDecimal 除法为某些计算返回错误结果
BigDecimal divide returning wrong result for some calculations
我已经阅读了一些关于浮点数及其数学的问题。在我看来,问题只发生在更大的范围内(即 10 位以上的小数)。现在我的问题已经发生在小数点后 2 位,并且在某些情况下会非常严重:
以下代码:
System.out.println(
String.format("%.2f", BigDecimal.valueOf(2.8).divide(BigDecimal.valueOf(3.87), 2).doubleValue()));
System.out.println(
String.format("%.2f", BigDecimal.valueOf(2.41).divide(BigDecimal.valueOf(2.73), 2).doubleValue()));
分别生成这个输出:
0.80
0.89
如果我手动执行计算(使用 google 计算),我得到这些结果:
0.72351421188
0.88278388278
第一次计算的结果非常大(~0.08 off),而第二次计算结果非常低(~0.01 off)。
对于为什么第一个结果那么大,是否有一些理智的解释?或者使用 BigDecimal
?
获得正确结果的任何方法
注意
System.out.println(2.8/3.87);
实际上 returns 正确的结果 (0.7235142118863048)。
另请注意,String.format
内容仅用于检查结果是否被它更改,事实并非如此。 BigDecimal.valueOf(2.8).divide(BigDecimal.valueOf(3.87), 2).doubleValue()
产生完全相同的结果。
问题是您使用了错误的 BigDecimal#divide
四舍五入到小数点后两位。以下是 BigDecimal#divide
方法的可用参数:
divide(BigDecimal divisor)
divide(BigDecimal divisor, int roundingMode)
divide(BigDecimal divisor, MathContext mc)
divide(BigDecimal divisor, RoundingMode roundingMode)
divide(BigDecimal divisor, int scale, int roundingMode)
divide(BigDecimal divisor, int scale, RoundingMode roundingMode)
由于您将 divide
与 BigDecimal
和 int
参数一起使用,因此它使用 divide(BigDecimal divisor, int roundingMode)
,其中您的 2
是舍入模式和 NOT 比例。在这种情况下,2
实际上是 ROUND_CEILING
,并且未指定比例。
相反,您必须使用 divide(BigDecimal divisor, int scale, int roundingMode)
或 divide(BigDecimal divisor, int scale, RoundingMode roundingMode)
。因此,将您的呼叫更改为:
System.out.println(BigDecimal.valueOf(2.8).divide(BigDecimal.valueOf(3.87), 2,
BigDecimal.ROUND_HALF_UP));
System.out.println(BigDecimal.valueOf(2.41).divide(BigDecimal.valueOf(2.73), 2,
BigDecimal.ROUND_HALF_UP));
(请随意使用 ROUND_HALF_UP
以外的舍入模式。)
我不确定它默认使用什么比例,但是你在 2
中指定的 ROUND_CEILING
导致了你的计算问题。
至于提到的评论,可以通过三种可能的方式创建具有指定值的 BigDecimal
:
BigDecimal.valueOf(2.8)
new BigDecimal(2.8)
new BigDecimal("2.8")
new BigDecimal(2.8)
仍然给出浮点错误,所以我建议像您已经使用的那样使用 BigDecimal.valueOf(2.8)
,或者使用字符串构造函数 new BigDecimal("2.8")
.
然而,这与您的舍入问题有点无关,因为使用正确的 divide
方法将给出正确的结果,而不管您使用的 BigDecimal
初始化如何:
我已经阅读了一些关于浮点数及其数学的问题。在我看来,问题只发生在更大的范围内(即 10 位以上的小数)。现在我的问题已经发生在小数点后 2 位,并且在某些情况下会非常严重:
以下代码:
System.out.println(
String.format("%.2f", BigDecimal.valueOf(2.8).divide(BigDecimal.valueOf(3.87), 2).doubleValue()));
System.out.println(
String.format("%.2f", BigDecimal.valueOf(2.41).divide(BigDecimal.valueOf(2.73), 2).doubleValue()));
分别生成这个输出:
0.80
0.89
如果我手动执行计算(使用 google 计算),我得到这些结果:
0.72351421188
0.88278388278
第一次计算的结果非常大(~0.08 off),而第二次计算结果非常低(~0.01 off)。
对于为什么第一个结果那么大,是否有一些理智的解释?或者使用 BigDecimal
?
注意
System.out.println(2.8/3.87);
实际上 returns 正确的结果 (0.7235142118863048)。
另请注意,String.format
内容仅用于检查结果是否被它更改,事实并非如此。 BigDecimal.valueOf(2.8).divide(BigDecimal.valueOf(3.87), 2).doubleValue()
产生完全相同的结果。
问题是您使用了错误的 BigDecimal#divide
四舍五入到小数点后两位。以下是 BigDecimal#divide
方法的可用参数:
divide(BigDecimal divisor)
divide(BigDecimal divisor, int roundingMode)
divide(BigDecimal divisor, MathContext mc)
divide(BigDecimal divisor, RoundingMode roundingMode)
divide(BigDecimal divisor, int scale, int roundingMode)
divide(BigDecimal divisor, int scale, RoundingMode roundingMode)
由于您将 divide
与 BigDecimal
和 int
参数一起使用,因此它使用 divide(BigDecimal divisor, int roundingMode)
,其中您的 2
是舍入模式和 NOT 比例。在这种情况下,2
实际上是 ROUND_CEILING
,并且未指定比例。
相反,您必须使用 divide(BigDecimal divisor, int scale, int roundingMode)
或 divide(BigDecimal divisor, int scale, RoundingMode roundingMode)
。因此,将您的呼叫更改为:
System.out.println(BigDecimal.valueOf(2.8).divide(BigDecimal.valueOf(3.87), 2,
BigDecimal.ROUND_HALF_UP));
System.out.println(BigDecimal.valueOf(2.41).divide(BigDecimal.valueOf(2.73), 2,
BigDecimal.ROUND_HALF_UP));
(请随意使用 ROUND_HALF_UP
以外的舍入模式。)
我不确定它默认使用什么比例,但是你在 2
中指定的 ROUND_CEILING
导致了你的计算问题。
至于提到的评论,可以通过三种可能的方式创建具有指定值的 BigDecimal
:
BigDecimal.valueOf(2.8)
new BigDecimal(2.8)
new BigDecimal("2.8")
new BigDecimal(2.8)
仍然给出浮点错误,所以我建议像您已经使用的那样使用 BigDecimal.valueOf(2.8)
,或者使用字符串构造函数 new BigDecimal("2.8")
.
然而,这与您的舍入问题有点无关,因为使用正确的 divide
方法将给出正确的结果,而不管您使用的 BigDecimal
初始化如何: