双精度浮点数真的不适合处理货币吗?

72

我经常告诉别人,在C#中,double类型的变量不适合用于表示金钱。可能会发生各种奇怪的事情。但是我似乎无法创建一个示例来演示这些问题。有人能提供这样的示例吗?

(编辑:此帖最初被标记为C#;一些回复涉及到decimal的具体细节,因此意味着System.Decimal)。

(编辑2:我特别要求一些C#代码,所以我认为这不仅是与语言无关的)


为什么不使用Double或Float来表示货币? - phuclv
8个回答

120

非常不适合。请使用十进制。

double x = 3.65, y = 0.05, z = 3.7;
Console.WriteLine((x + y) == z); // false

(Jon的页面上的示例在这里 - 推荐阅读 ;-p)


48
该死,如果我知道我自己的页面上有一个例子,我就不会想出另一个例子了 ;) - Jon Skeet

35

由于舍入效果,您将获得奇怪的错误。此外,与精确值的比较非常棘手 - 通常需要应用某种epsilon来检查实际值是否“接近”特定值。

以下是一个具体示例:

using System;

class Test
{
    static void Main()
    {
        double x = 0.1;
        double y = x + x + x;
        Console.WriteLine(y == 0.3); // Prints False
    }
}

如果您正在使用返回您无法控制的双精度货币值的服务,那么在将它们转换为十进制数时需要考虑哪些问题?例如精度损失等。 - vikingben
1
@vikingben:绝对没错 - 从根本上讲,这是一种错误的做法,你需要想办法找出最佳的数据解释方式。 - Jon Skeet

7

是的,它不适合。

如果我没记错的话,double类型有大约17个有效数字,因此通常舍入误差会发生在小数点后很远的地方。大多数金融软件在小数点后使用4位数字,这样留下13个数字可以用来进行单个操作,因此最大的可工作数字仍然比美国国家债务高得多。但是随着时间的推移,舍入误差将累积起来。如果您的软件运行时间很长,最终会开始损失几分钱。某些操作会使情况变得更糟。例如,将大量添加到小量将导致显着的精度损失。

您需要使用固定点数据类型进行货币运算,大多数人不介意您偶尔丢失一分钱,但会计师与大多数人不同。

编辑
根据此网站http://msdn.microsoft.com/en-us/library/678hzkk9.aspx Double实际上具有15到16个有效数字而不是17。

@ Jon Skeet由于decimal类型具有更高的精度,即28或29个有效数字,因此它比double类型更适合。这意味着积累的舍入误差变得不那么重要。正如Boojum提到的那样,固定点数据类型(例如表示美分或1/100美元的整数)实际上更适合。


1
请注意,System.Decimal是.NET中建议使用的类型,它仍然是一种浮点类型 - 但它是一个浮动的十进制点而不是一个浮动的二进制点。在大多数情况下,这比具有固定精度更重要,我认为。 - Jon Skeet
1
这正是问题所在。现今货币通常是十进制的,但在美国证券市场实施十进制化之前,使用的是二进制分数(我曾经看到过256分之一甚至1024分之一),因此对于股价而言,双精度浮点数比小数更为合适。不过,在英镑实行十进制化之前,960个法令等于一磅真的很麻烦;那既不是十进制也不是二进制,但它确实提供了丰富的质因数,方便进行分数计算。 - Jeffrey Hantin
1
比仅仅是一个十进制浮点数更重要的是,表达式x + 1 != x总是成立。此外,它保留精度,因此您可以区分11.0 - Gabe
@Gabe:只有在将值按比例缩小,使值为1表示最小货币单位时,这些属性才有意义。Decimal值可能会失去小数点右侧的精度,而不表示任何问题。 - supercat
double类型仅考虑整数值时具有15.9个有效十进制数字。小数点后的情况取决于数值。 - user207421

5
由于十进制使用10的倍数作为比例因子,所以像0.1这样的数字可以被准确地表示。实质上,十进制类型将其表示为1 / 10 ^ 1,而double类型则将其表示为104857 / 2 ^ 20(实际上更像是really-big-number / 2 ^ 1023)。 decimal可以准确地表示任何带有28/29个有效数字的基数10值(如0.1),但double不能。

3
十进制并没有96个有效数字,它有96个有效比特。十进制大约有28个有效数字。 - Jon Skeet
你是在谈论哪种语言的十进制类型?或者所有支持此类型的语言都以完全相同的方式支持它吗?建议明确说明。 - Adam Davis
@Adam - 这篇帖子最初有C#标签,所以我们特别谈论System.Decimal。 - Marc Gravell
哎呀,Jon发现得好!已经更正了。Adam,我在谈论C#,就像问题中所述。其他语言有叫做decimal的类型吗? - Richard Poole
@Richard: 嗯,所有基于 .NET 的语言都有,因为 System.Decimal 不是 C# 独有的类型,它是 .NET 类型。 - awe
@awe - 我的意思是非.NET语言。我无知地不知道有哪些具有本地基于10进制浮点类型的语言,但我毫不怀疑它们存在。 - Richard Poole

4

我的理解是大多数金融系统使用整数来表示货币,即以分为单位计算。

IEEE双精度实际上可以在范围-2^53到+2^53内准确地表示所有整数。(《黑客的乐趣》,第262页)如果您仅使用加法、减法和乘法,并将所有内容保持在此范围内的整数,则不应看到任何精度损失。但是,我会非常谨慎地处理除法或更复杂的操作。


如果你只打算使用整数,为什么不一开始就使用整数类型呢? - Jon Skeet
2
嘿 - int64_t 可以精确表示范围在-2^63到+2^63-1之间的所有整数。如果您仅使用加法,减法和乘法,并将所有内容保持在此范围内的整数,则不应看到任何精度损失。但是,我会非常谨慎地使用除法。 - Steve Jessop
一些过时的系统(遗憾地)仍在使用double,但不支持任何64位整数类型。我建议将计算作为double进行,缩放以使任何语义所需的舍入始终是整个单位,这可能是最有效的方法。 - supercat

4

如果你不知道在做什么的情况下使用double是不合适的。

"double"可以表示1万亿美元的金额,误差只有1/90美分。因此,您将获得高精度的结果。想要计算将一个人送上火星并使他安全返回所需的成本吗?double就可以胜任。

但是对于货币而言,通常有非常具体的规定,规定必须给出某个特定的计算结果,而不能是其他的结果。如果你计算的金额非常非常接近98.135美元,那么通常会有一个规则确定结果应该是98.14美元还是98.13美元,你必须遵循这个规则并得到所需的结果。

根据您所在的位置,使用64位整数来表示分或便士或kopeks或您所在国家的最小单位通常可以正常工作。例如,64位带符号整数表示的分可以表示价值高达92,223万亿美元的价值。32位整数通常不适用。


更有意义 - Lujun Weng

0

一个双精度浮点数总是会有舍入误差,如果你在 .Net 上,使用 "decimal" 类型。


6
注意。任何浮点数表示法都会存在舍入误差,包括十进制。只是十进制会以对人类直观的方式进行四舍五入(通常适用于货币),而二进制浮点数则不会。但对于非金融数字计算,即使在C#中,双精度浮点数通常比十进制更好得多。 - Daniel Pryden

-5

实际上,如果选择合适的单位,浮点数double非常适合表示货币金额。

请参见http://www.idinews.com/moneyRep.html

固定点数long也是如此。两者都占用8个字节,肯定比decimal项占用的16个字节更可取。

某种技术是否有效(即产生预期和正确的结果)不是投票或个人偏好的问题。一种技术要么有效,要么无效。


1
链接你写的一篇文章,反对几十年来的常见做法和专家意见,即浮点数不适合用于金融交易表示,需要更多的支持。 - MuertoExcobito

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