我总是被告知永远不要用双类型或浮点类型来表示货币,这一次我向你提出一个问题:为什么?
我相信有一个很好的理由,我只是不知道是什么。
我总是被告知永远不要用双类型或浮点类型来表示货币,这一次我向你提出一个问题:为什么?
我相信有一个很好的理由,我只是不知道是什么。
当前回答
我对其中一些回答感到困扰。我认为双数和浮点数在财务计算中占有一席之地。当然,在使用整数类或BigDecimal类时,在加减非分数货币金额时,不会损失精度。但是,当执行更复杂的操作时,无论您如何存储这些数字,您经常会得到小数点后几位或许多位的结果。问题在于你如何呈现结果。
如果你的结果是在四舍五入和四舍五入之间的边缘,最后一分真的很重要,你可能应该告诉观众答案几乎在中间——通过显示更多的小数点后数位。
双精度浮点数的问题是,当它们被用来组合大数和小数时。在java中,
System.out.println(1000000.0f + 1.2f - 1000000.0f);
结果
1.1875
其他回答
为了补充前面的答案,在处理问题中解决的问题时,除了BigDecimal之外,还可以选择在Java中实现Joda-Money。Java模块名称为org.joda.money。
它需要Java SE 8或更高版本,并且没有依赖关系。
更准确地说,存在编译时依赖关系,但它不是 必需的。
<dependency>
<groupId>org.joda</groupId>
<artifactId>joda-money</artifactId>
<version>1.0.1</version>
</dependency>
使用Joda Money的例子:
// 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();
文档: http://joda-money.sourceforge.net/apidocs/org/joda/money/Money.html 实现示例: https://www.programcreek.com/java-api-examples/?api=org.joda.money.Money
如果你的计算涉及到不同的步骤,任意的精度算法都不能100%覆盖你。
使用完美的结果表示(使用自定义Fraction数据类型,将除法操作批处理到最后一步)并且仅在最后一步转换为十进制的唯一可靠方法。
任意精度不会有帮助,因为总有可能有很多小数点后的数字,或者一些结果,如0.6666666……最后一个例子没有任意的表示法。所以每一步都会有小误差。
这些错误会累积起来,最终可能变得不再容易被忽视。这被称为错误传播。
这不是精确与否的问题,也不是精确与否的问题。这是一个满足以10为底而不是以2为底计算的人的期望的问题。例如,在财务计算中使用双精度值不会产生数学意义上的“错误”答案,但它可以产生财务意义上不期望的答案。
即使您在输出前的最后一分钟舍入结果,您仍然可以偶尔使用与期望不匹配的双精度结果。
Using a calculator, or calculating results by hand, 1.40 * 165 = 231 exactly. However, internally using doubles, on my compiler / operating system environment, it is stored as a binary number close to 230.99999... so if you truncate the number, you get 230 instead of 231. You may reason that rounding instead of truncating would have given the desired result of 231. That is true, but rounding always involves truncation. Whatever rounding technique you use, there are still boundary conditions like this one that will round down when you expect it to round up. They are rare enough that they often will not be found through casual testing or observation. You may have to write some code to search for examples that illustrate outcomes that do not behave as expected.
Assume you want to round something to the nearest penny. So you take your final result, multiply by 100, add 0.5, truncate, then divide the result by 100 to get back to pennies. If the internal number you stored was 3.46499999.... instead of 3.465, you are going to get 3.46 instead 3.47 when you round the number to the nearest penny. But your base 10 calculations may have indicated that the answer should be 3.465 exactly, which clearly should round up to 3.47, not down to 3.46. These kinds of things happen occasionally in real life when you use doubles for financial calculations. It is rare, so it often goes unnoticed as an issue, but it happens.
如果您使用以10为基数进行内部计算,而不是使用双数,则如果您的代码中没有其他错误,那么结果总是完全符合人类的预期。
摘自Bloch, J., Effective Java,(第二版,第48项。第3版,项目60):
float和double类型是 尤其不适用于货币 因为这是不可能的 表示0.1(或任何其他。 10的负次方)作为浮点数或 完全的两倍。 例如,假设您有1.03美元 你花了42c。多少钱? 你走了? System.out.println(1.03 - .42); 输出0.6100000000000001。 解决这个问题的正确方法是 使用BigDecimal, int或long 用于货币计算。
虽然BigDecimal有一些警告(请参阅当前接受的答案)。
浮点数和双精度数是近似的。如果你创建了一个BigDecimal并将一个float传递给构造函数,你会看到float实际等于什么:
groovy:000> new BigDecimal(1.0F)
===> 1
groovy:000> new BigDecimal(1.01F)
===> 1.0099999904632568359375
这可能不是您想要的表示1.01美元的方式。
问题是IEEE规范没有一种方法来精确地表示所有的分数,其中一些分数最终是重复的分数,所以你最终会得到近似错误。由于会计人员喜欢精确到每一分钱,如果客户支付账单,在付款处理后他们欠0.01,他们会被收取费用或无法关闭他们的帐户,那么最好使用精确的类型,如decimal(在c#中)或Java. math. bigdecimal。
这并不是说如果你四舍五入,误差就无法控制:请参阅Peter Lawrey的这篇文章。只是从一开始就不用四舍五入更容易。大多数处理资金的应用程序不需要大量的数学运算,操作包括添加东西或将金额分配到不同的存储空间。引入浮点数和舍入只会使事情复杂化。