为什么不能使用Double或Float来表示货币?

1217

我一直被告知不要使用 doublefloat 类型来表示货币,这次我向您提出问题:为什么?

我相信有很好的原因,只是我不知道是什么。


5
请查看此 Stack Overflow 问题:Rounding Errors? - Jeff Ogata
103
需要翻译的内容:Just to be clear, they shouldn't be used for anything that requires accuracy -- not just currency.为了明确起见,它们不应该被用于任何需要准确性的事情,不仅限于货币。 - Jeff
193
它们不应用于需要精确性的任何事情。但是双精度浮点数的53个有效位(约16个十进制数字)通常足够处理那些仅需要准确性的事情。 - dan04
39
@jeff你的评论完全误解了二进制浮点数的适用和不适用范围。请仔细阅读下面zneak的回答,并删除你误导性的评论。 - Pascal Cuoq
1
请明确一点,您所说的“exactness”(或“precision”)是指小数点位数的准确性。 - Jack Leow
16个回答

5

如果您的计算涉及多个步骤,则任意精度算术无法100%涵盖您。

唯一可靠的方法是使用完美结果表示方式(使用自定义分数数据类型,将批处理除法操作到最后一步),并仅在最后一步转换为十进制表示法。

任意精度无法帮助,因为总会存在具有多个小数位的数字或某些结果(例如0.6666666 ...),任何任意表示都无法覆盖最后一个示例。因此,每个步骤都会产生小错误。

这些误差会累加,可能最终变得难以忽略。这被称为误差传播


3
许多回答此问题的帖子都讨论了IEEE和浮点算术标准。
作为一个非计算机科学背景(物理和工程)的人,我往往从不同的角度看待问题。对我来说,我不会在数学计算中使用double或float的原因是会丢失太多信息。
那有什么替代方案呢?有很多(我还不知道的更多!)。
Java中的BigDecimal是Java语言的本地类型。 Apfloat是Java的另一个任意精度库。
C#中的十进制数据类型是Microsoft.NET的替代品,具有28位有效数字。
SciPy(Scientific Python)可能也可以处理金融计算(我没有尝试过,但我认为是这样的)。
GNU多精度库(GMP)和GNU MFPR库是C和C ++的两个免费且开源的资源。
JavaScript(!)和PHP也有数值精度库,可以处理金融计算。
许多计算机语言也有专有的(特别是FORTRAN)和开源的解决方案。
虽然我不是一名计算机科学家,但我倾向于使用Java中的BigDecimal或C#中的十进制类型。我还没有尝试过我列出的其他解决方案,但它们可能也很好。
对我来说,我喜欢BigDecimal,因为它支持的方法。 C#的十进制很好,但我还没有像我想象的那样使用过它。我在业余时间进行科学计算,BigDecimal似乎非常有效,因为我可以设置浮点数的精度。 BigDecimal的缺点是什么?有时会很慢,特别是如果您使用除法方法。
为了提高速度,您可以查看C,C ++和Fortran中的免费和专有库。

3
关于SciPy/Numpy,固定精度(即Python的decimal.Decimal)不受支持(http://docs.scipy.org/doc/numpy-dev/user/basics.types.html)。一些函数不能与Decimal正常工作(例如isnan)。Pandas基于Numpy,并在AQR发起,这是一个主要的量化对冲基金。因此,您已经得到了有关财务计算(而不是杂货会计)的答案。 - comte

2

请看这个简单的例子:它看起来逻辑上是正确的,但在现实世界中,如果没有得到正确对待,它可能会返回意外结果:

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... 

5
问题不在于存在舍入误差,而在于您没有处理它。将结果四舍五入到两位小数(如果要表示分),问题就解决了。 - maaartinus

2
补充之前的答案,当处理问题时,在Java中还有实现Joda-Money的选项,除了BigDecimal。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

0

浮点数是十进制的二进制形式,它们是两个不同的东西。在相互转换时,两种类型之间存在一些小误差。此外,浮点数被设计用于表示科学中无限大的值。这意味着它被设计为在固定字节数的情况下失去极小和极大数字的精度。十进制不能表示无限数量的值,它只能绑定到那些十进制数字。因此,浮点数和十进制数是为不同目的而设计的。

有一些方法可以管理货币价值的误差:

  1. 使用长整型并计算分数。

  2. 使用双精度,仅保留15个有效数字,以便可以准确模拟小数。在呈现值之前进行四舍五入;在进行计算时经常进行四舍五入。

  3. 使用像Java BigDecimal这样的十进制库,这样您就不需要使用双精度来模拟小数。

p.s. 有趣的是,大多数手持科学计算器品牌都使用十进制而不是浮点数。因此,没有人抱怨浮点数转换错误。


-1

美元可以用美分和美元来表示。整数是100%精确的,而浮点二进制数与浮点十进制数并不完全匹配。


错误。整数并非100%精确。精度需要小数或分数。 - alexherm
它们在货币等整数值方面非常精确。 - RollerSimmer

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