如何使用Java的BigDecimal进行正确的计算?

3

据我理解,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知道有两个小数位。这至少是我期望的。

如何使用BigDecimal正确解决这个问题编辑:需要以两个double值作为输入,并且已知小数点后的位数? [即,因为API没有给我除这两个double之外的任何东西] (我知道有其他可靠地计算货币的方法(从以int形式计算分开始),但这超出了我的问题范围。)

3个回答

4

出于这个原因,请不要使用带有double参数的BigDecimal构造函数:

  • 这个构造函数的结果可能会有些不可预测。人们可能会认为在Java中编写new BigDecimal(0.1)会创建一个BigDecimal,它恰好等于0.1(一个无标度值为1,具有标度1),但实际上等于0.1000000000000000055511151231257827021181583404541015625。这是因为0.1不能被精确地表示为double(或者说,不能被精确表示为任何有限长度的二进制分数)。因此,传递给构造函数的值并不完全等于0.1,尽管表面上看起来是这样。

请使用带有String参数的构造函数

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

1

正如@Progman所写,使用双重构造函数将创建double值的精确十进制表示形式,但是文档建议不要使用

然而,你得到11而不是11.22的原因是你将MathContext的精度设置为2。
精度是使用的数字数量,而不是小数位数。因此,如果你将代码更改为使用4作为精度,则会得到输出

a: 11.22
b: 11.260000000000000000832667268468867405317723751068115234375

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

精度的定义为数字的位数在 MathContext类文档文档中说明。


1
使用双精度值而非字符串作为参数传递给 BigDecimal 存在问题,因为这些值通常不是精确的,会导致循环小数。实际上,只有分母是 52 的浮点数才能被表示为浮点数或双精度浮点数的精确值(例如 1/20 = .05,1/4 = .25,1/5 = .2)。这是因为 52 是基于十进制的唯一质因子,并且将返回一个有限(即非重复)的分数展开形式。任何其他值都会导致循环小数(例如 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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接