【问题标题】:How do I calculate correctly with Java’s BigDecimal? [duplicate]如何使用 Java 的 BigDecimal 正确计算? [复制]
【发布时间】:2020-10-26 13:15:04
【问题描述】:

据我了解,BigDecimal 用于正确处理具有固定小数位的数字(即金钱)。这是我写的一个小程序:

import java.math.*;

public class Main {
    public static void main(String[] args) {
        BigDecimal a = new BigDecimal(11.22, new MathContext(2, RoundingMode.HALF_UP));
        System.out.println("a: " + a);
        BigDecimal b = a.add(new BigDecimal(0.04));
        System.out.println("b: " + b);
    }
}

我希望看到:

a: 11.22
b: 11.26

但我得到的是:

a: 11
b: 11.040000000000000000832667268468867405317723751068115234375

我将a 设置为有两位小数,但它都不会打印它们,甚至会忘记它们并四舍五入为纯整数。为什么? b 应该加上 0.04 并从 a 知道也有两位小数。这至少是我所期望的。

如何使用 BigDecimaledit: 以两个双精度值作为输入和已知的小数位数正确解决这个问题? [即,因为 API 除了这两个双精度数之外没有给我任何东西。](我知道还有其他可靠的方法可以用钱计算(从用int 以美分计算开始),但这已经超出了我的问题的范围。)

【问题讨论】:

    标签: java bigdecimal


    【解决方案1】:

    出于这个原因,请勿使用BigDecimal constructor with the double argument

    • 此构造函数的结果可能有些难以预测。有人可能会假设用 Java 编写 new BigDecimal(0.1) 会创建一个 BigDecimal,它正好等于 0.11 的未缩放值,缩放为 1),但它实际上等于 0.1000000000000000055511151231257827021181583404541015625。这是因为0.1 不能完全表示为double(或者,就此而言,不能表示为任何有限长度的二进制分数)。因此,传递给构造函数的值并不完全等于0.1,尽管看起来如此。

    使用constructor with the String argument

    public static void main(String[] args) {
        BigDecimal a = new BigDecimal("11.22");
        System.out.println("a: " + a);
        BigDecimal b = a.add(new BigDecimal("0.04"));
        System.out.println("b: " + b);
    }
    

    这将按预期生成输出:

    a: 11.22
    b: 11.26
    

    【讨论】:

      【解决方案2】:

      正如@Progman 还写的那样,使用双精度构造函数将创建双精度值的精确十进制表示,它是advised against in the documentation

      但是,您得到 11 而不是 11.22 的原因是您已将 MathContext 的精度设置为 2。
      精度是使用的位数,而不是小数位数。因此,如果您将代码更改为使用 4 作为精度,那么您将获得输出

      a: 11.22
      b: 11.260000000000000000832667268468867405317723751068115234375
      

      仍然存在使用双精度值的问题,但现在有更多小数位!

      精度为位数的定义在MathContext class documentationdocumentation中

      【讨论】:

        【解决方案3】:

        使用双精度值而不是字符串作为BigDecimal 的参数的问题在于它们大多不精确,并且会导致重复的十进制扩展。事实上,只有分母中 5 和或 2 的幂的浮点数才能精确表示为浮点数或双精度数(例如 1/20 = .05 1/4 = .25 , 1/5 = .2)。这是因为52 是基数10 的唯一主要因素,并且将返回分数的有限(即非重复)展开。任何其他值都会导致重复的十进制扩展(例如 1/3 = .3333333333、1/6 = .16666666),这就是导致您的问题的原因。

        通过指定字符串而不是双精度值,BigDecimal 可以对预期的期望值进行操作,而不是它限制或无法控制的二进制值。

        你的价值观是否如下。

        BigDecimal a = new BigDecimal(11.25);             
        System.out.println("a: " + a);
        BigDecimal b = a.add(new BigDecimal(.50));
        System.out.println("b: " + b);
        

        输出应该是

        11.25
        11.75
        

        因为小数部分的结果总和只有 5 和 2 作为除数。

        因此,您应该在初始化BigDecimal 对象时指定浮点值的字符串表示形式。

        有关浮点数内部表示的更多信息,请查看IEEE 754

        【讨论】:

          猜你喜欢
          • 2021-12-07
          • 1970-01-01
          • 1970-01-01
          • 2013-12-25
          • 1970-01-01
          • 2014-10-04
          • 2014-06-06
          • 2014-12-13
          • 2016-08-13
          相关资源
          最近更新 更多