如何使用 Java 的 BigDecimal 正确计算?
How do I calculate correctly with Java’s BigDecimal?
据我了解,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 edit: 将两个双精度值作为输入并已知小数位数来正确解决这个问题? [即,因为 API 除了这两个双打之外没有给我任何其他东西。](我知道还有其他方法可以可靠地用钱计算(从用 [=18= 计算美分开始) ]), 但这超出了我的问题范围。)
不要使用 BigDecimal constructor with the double
argument 正是因为这个原因:
- The results of this constructor can be somewhat unpredictable. One might assume that writing
new BigDecimal(0.1)
in Java creates a BigDecimal which is exactly equal to 0.1
(an unscaled value of 1
, with a scale of 1
), but it is actually equal to 0.1000000000000000055511151231257827021181583404541015625
. This is because 0.1
cannot be represented exactly as a double
(or, for that matter, as a binary fraction of any finite length). Thus, the value that is being passed in to the constructor is not exactly equal to 0.1
, appearances notwithstanding.
使用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
正如@Progman 所写,使用 double 构造函数将创建 double 值的精确十进制表示,它是 advised against in the documentation。
然而,您得到 11 而不是 11.22 的原因是您已将 MathContext 的精度设置为 2。
精度是使用的位数,而不是小数位数。因此,如果您将代码更改为使用 4 作为精度,那么您将获得输出
a: 11.22
b: 11.260000000000000000832667268468867405317723751068115234375
仍然存在使用双精度值的问题,但现在有更多的小数位!
精度作为位数的定义在MathContext class documentation文档
中
使用双精度值而不是字符串作为 BigDecimal
的参数的问题在于它们大多不准确,并且会导致重复的小数扩展。事实上,只有分母中 5
和或 2
的幂的浮点数才能精确表示为浮点数或双精度数(例如 1/20 = .05 1/4 = .25 , 1/5 = .2)。这是因为 5
和 2
是基数 10
的唯一质因数,并且 return 将是分数的有限(即 non-repeating)展开。任何其他值都会导致重复的小数扩展(例如 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
据我了解,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 edit: 将两个双精度值作为输入并已知小数位数来正确解决这个问题? [即,因为 API 除了这两个双打之外没有给我任何其他东西。](我知道还有其他方法可以可靠地用钱计算(从用 [=18= 计算美分开始) ]), 但这超出了我的问题范围。)
不要使用 BigDecimal constructor with the double
argument 正是因为这个原因:
- The results of this constructor can be somewhat unpredictable. One might assume that writing
new BigDecimal(0.1)
in Java creates a BigDecimal which is exactly equal to0.1
(an unscaled value of1
, with a scale of1
), but it is actually equal to0.1000000000000000055511151231257827021181583404541015625
. This is because0.1
cannot be represented exactly as adouble
(or, for that matter, as a binary fraction of any finite length). Thus, the value that is being passed in to the constructor is not exactly equal to0.1
, appearances notwithstanding.
使用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
正如@Progman 所写,使用 double 构造函数将创建 double 值的精确十进制表示,它是 advised against in the documentation。
然而,您得到 11 而不是 11.22 的原因是您已将 MathContext 的精度设置为 2。
精度是使用的位数,而不是小数位数。因此,如果您将代码更改为使用 4 作为精度,那么您将获得输出
a: 11.22
b: 11.260000000000000000832667268468867405317723751068115234375
仍然存在使用双精度值的问题,但现在有更多的小数位!
精度作为位数的定义在MathContext class documentation文档
中使用双精度值而不是字符串作为 BigDecimal
的参数的问题在于它们大多不准确,并且会导致重复的小数扩展。事实上,只有分母中 5
和或 2
的幂的浮点数才能精确表示为浮点数或双精度数(例如 1/20 = .05 1/4 = .25 , 1/5 = .2)。这是因为 5
和 2
是基数 10
的唯一质因数,并且 return 将是分数的有限(即 non-repeating)展开。任何其他值都会导致重复的小数扩展(例如 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