我一直被告知不要使用 double
或 float
类型来表示货币,这次我向您提出问题:为什么?
我相信有很好的原因,只是我不知道是什么。
我一直被告知不要使用 double
或 float
类型来表示货币,这次我向您提出问题:为什么?
我相信有很好的原因,只是我不知道是什么。
如果您的计算涉及多个步骤,则任意精度算术无法100%涵盖您。
唯一可靠的方法是使用完美结果表示方式(使用自定义分数数据类型,将批处理除法操作到最后一步),并仅在最后一步转换为十进制表示法。
任意精度无法帮助,因为总会存在具有多个小数位的数字或某些结果(例如0.6666666
...),任何任意表示都无法覆盖最后一个示例。因此,每个步骤都会产生小错误。
这些误差会累加,可能最终变得难以忽略。这被称为误差传播。
请看这个简单的例子:它看起来逻辑上是正确的,但在现实世界中,如果没有得到正确对待,它可能会返回意外结果:
0.1 x 10 = 1,所以:
double total = 0.0;
// adds 10 cents, 10 times
for (int i = 0; i < 10; i++) {
total += 0.1; // adds 10 cents
}
Log.d("result: ", "current total: " + total);
// looks like total equals to 1.0, don't?
// now, do reverse
for (int i = 0; i < 10; i++) {
total -= 0.1; // removes 10 cents
}
// total should be equals to 0.0, right?
Log.d("result: ", "current total: " + total);
if (total == 0.0) {
Log.d("result: ", "is total equal to ZERO? YES, of course!!");
} else {
Log.d("result: ", "is total equal to ZERO? No...");
// so be careful comparing equality in this cases!!!
}
result: current total: 0.9999999999999999
result: current total: 2.7755575615628914E-17
result: is total equal to ZERO? No...
它需要Java SE 8或更高版本,并且没有任何依赖关系。
更准确地说,它有一个编译时依赖关系,但不是必需的。
<dependency>
<groupId>org.joda</groupId>
<artifactId>joda-money</artifactId>
<version>1.0.1</version>
</dependency>
// create a monetary value
Money money = Money.parse("USD 23.87");
// add another amount with safe double conversion
CurrencyUnit usd = CurrencyUnit.of("USD");
money = money.plus(Money.of(usd, 12.43d));
// subtracts an amount in dollars
money = money.minusMajor(2);
// multiplies by 3.5 with rounding
money = money.multipliedBy(3.5d, RoundingMode.DOWN);
// compare two amounts
boolean bigAmount = money.isGreaterThan(dailyWage);
// convert to GBP using a supplied rate
BigDecimal conversionRate = ...; // obtained from code outside Joda-Money
Money moneyGBP = money.convertedTo(CurrencyUnit.GBP, conversionRate, RoundingMode.HALF_UP);
// use a BigMoney for more complex calculations where scale matters
BigMoney moneyCalc = money.toBigMoney();
浮点数是十进制的二进制形式,它们是两个不同的东西。在相互转换时,两种类型之间存在一些小误差。此外,浮点数被设计用于表示科学中无限大的值。这意味着它被设计为在固定字节数的情况下失去极小和极大数字的精度。十进制不能表示无限数量的值,它只能绑定到那些十进制数字。因此,浮点数和十进制数是为不同目的而设计的。
有一些方法可以管理货币价值的误差:
使用长整型并计算分数。
使用双精度,仅保留15个有效数字,以便可以准确模拟小数。在呈现值之前进行四舍五入;在进行计算时经常进行四舍五入。
使用像Java BigDecimal这样的十进制库,这样您就不需要使用双精度来模拟小数。
p.s. 有趣的是,大多数手持科学计算器品牌都使用十进制而不是浮点数。因此,没有人抱怨浮点数转换错误。
美元可以用美分和美元来表示。整数是100%精确的,而浮点二进制数与浮点十进制数并不完全匹配。