英文:
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
解决这个问题呢?***编辑:***输入为两个双精度浮点数,并且已知小数位数?(我知道有其他可靠地处理货币计算的方法(例如从以“分”为单位进行计算,使用 int
类型),但这超出了我问题的范围。)
英文:
As I understood it, BigDecimal
is there for properly dealing with numbers with fixed decimal places (i.e. money). Here is a little program I wrote:
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);
}
}
I expected to see:
a: 11.22
b: 11.26
But what I got is:
a: 11
b: 11.040000000000000000832667268468867405317723751068115234375
I set a
to have two decimal places, but it both does not print them, and it even forgets them and rounds to plain int. Why? b
should add 0.04 and know from a
to have two decimal places, too. This was at least what I expected.
How is this solved correctly using BigDecimal edit: with two double values as input and a known number of decimal places? [I.e., because an API does not give me anything else than these two doubles.] (I know there are other ways to reliably calculate with money (starting from calculating in cents with int
), but that’s out of the scope of my question.)
答案1
得分: 4
不要使用带有double
参数的BigDecimal构造函数,就是因为这个原因:
- 这个构造函数的结果可能有些不可预测。有人可能会认为在Java中写入
new BigDecimal(0.1)
会创建一个精确等于0.1
的BigDecimal(无标度值为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
英文:
Do not use the BigDecimal constructor with the double
argument for exactly that reason:
> * 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.
Use the 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);
}
This will generate the output as expected:
a: 11.22
b: 11.26
答案2
得分: 1
如@Progman所写,使用双重构造函数将创建double值的精确十进制表示,而根据文档建议,不建议这样做。
然而,您之所以得到11而不是11.22,是因为您将MathContext的精度设置为2。
精度是使用的数字位数,而不是小数位数。因此,如果您将代码更改为使用4作为精度,那么您将获得输出
a: 11.22
b: 11.260000000000000000832667268468867405317723751068115234375
问题仍然是使用double值,但现在小数位数更多!
有关将精度定义为数字位数的定义在MathContext类文档中。
英文:
As @Progman also wrote, using the double constructor will create an exact decimal representation of the double value, and it is advised against in the documentation.
However the reason you are getting 11 instead of 11.22 is that you have set the precision of your MathContext to 2.
Precision is the number of digits used and not the number of decimal places. So if you changed your code to use 4 as the precision, then you would get the output
a: 11.22
b: 11.260000000000000000832667268468867405317723751068115234375
Still with the problem of the double value being used, but now with more decimal places!
The definition of the precision as the number of digits is in the MathContext class documentationdocumentation
答案3
得分: 1
使用双精度浮点数(double values)作为BigDecimal
的参数而不是字符串(strings)的问题在于,它们大多数情况下并不精确,并且会导致重复的小数展开。事实上,只有分母为5
和/或2
的浮点数幂可以被准确地表示为浮点数或双精度浮点数(例如,1/20 = 0.05,1/4 = 0.25,1/5 = 0.2)。这是因为5
和2
是基数10
的唯一素因子,将返回分数的有限(即非重复)展开。任何其他值都将导致循环小数展开(例如,1/3 = 0.3333333333,1/6 = 0.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。
英文:
The problem with using double values instead of strings as argument to BigDecimal
is that they are mostly not exact and will result in repeating decimal expansions. In fact, only floating point numbers that are powers of 5
and or 2
in the denominator can be represented exactly as a float or double (e.g 1/20 = .05 1/4 = .25 , 1/5 = .2). That is because 5
and 2
are the only prime factors of base 10
and will return a finite (i.e. non-repeating) expansion of the fraction. Any other value will result in a repeating decimal expansion (e.g. 1/3 = .3333333333, 1/6 = .16666666), and this is what is causing your problem.
By specifying Strings instead of doubles, BigDecimal
can operate on the expected desired value as opposed to the binary value to which it has limited or no control over.
Had your values been the following.
BigDecimal a = new BigDecimal(11.25);
System.out.println("a: " + a);
BigDecimal b = a.add(new BigDecimal(.50));
System.out.println("b: " + b);
The output would have been
11.25
11.75
Because both fractional parts and the resulting sum have only 5 and 2 as their divisors.
For this reason, you should specify string representation of floating point values when initializing BigDecimal
objects.
For more about internal representation of floating point numbers, check out IEEE 754
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论