十进制(decimal)和双精度浮点数(double)!- 我应该在什么时候使用它们?

1006

我经常看到人们在C#中使用double。我知道有些情况下double会丢失精度。 我的问题是什么时候应该使用double,什么时候应该使用decimal类型? 哪种类型适合于货币计算?(即大于1亿美元)

当需要进行科学计算或需要更大的范围时,应该使用double类型。而当需要高精度的小数计算时,则应该使用decimal类型。

对于货币计算,应该使用decimal类型。因为货币计算需要高精度小数,并且不能容忍任何形式的舍入误差。decimal类型可以提供可靠的结果,并且不会丢失精度。


8
你想要零点几美分的金额吗?(例如在加油站) - Daniel F. Thornton
https://dev59.com/d3RA5IYBdhLWcg3w2xwI - AaronS
4
实际上有一个相当简单的答案:十进制数像长整型和整型一样(它是一种整数类型!),但其语法和输出格式中有一个小数点(请参见http://en.wikipedia.org/wiki/Integer_(computer_science))。双精度浮点型和单精度浮点型使用尾数和指数(请参见http://en.wikipedia.org/wiki/Floating_point)。就是这样。 - atlaste
7个回答

1214

对于货币,始终使用十进制。这就是其被创建的原因。

如果数字必须正确相加或平衡,请使用十进制。这包括任何手动计算的财务存储或计算、分数或其他数字。

如果数字的精确值不重要,请为了提高速度而使用double。这包括图形、物理或其他物理科学计算中已经有“有效数字位数”的情况。


53
双精度浮点数并不是不准确的——它具有相对准确性,并且可以表示十进制无法完全处理的非常大或非常小的量级。 - Michael Borgwardt
100
使用十进制浮点数来表示货币的原因是,双精度浮点数的精度仅有16个十进制数字。在进行几次算术操作之后,误差就会迅速积累到足以影响到15、14、13位等数字的程度。将值舍入到“分”级别需要至少保留一位完整的小数位,而实际上您应该保留4或5个小数位来避免累积算术误差对用于舍入分数的百分位数的破坏。这样,您的货币只有 16(总位数)- 2(代表分钱的位数)- (4 或 5 位误差填充)= 只剩下7个(或更少)可靠的整数位数! - Triynko
27
因此,我不会操纵超过9.99美元的货币价值(1个整数位),因为我希望有更多的误差积累填充,大约是10或11个数字。由于十进制数是128位的数字,它能够给你这种隔离效果,即使涉及数万亿美元的数字,因为它具有28-29位的精度。但是,你不能比这更高。要正确舍入999,999,999,999,999.99R(999万亿),需要18位精度,而十进制数只提供28-29位,因此只有10位累计算术误差绝缘。 - Triynko
64
只是为了加深印象......如果你正在制作一个游戏,你是否真的会在意由于数百个“位置 +(速度×时间)”步骤中的累积误差而导致你所发射的爆炸桶在场地上飞行一英里后偏离目标1/16英寸?我觉得不会。 - Triynko
26
澄清一下,double类型并不具有16位数字——那只是有效数字的数量。浮点数是基于二进制幂的数学运算——某些十进制数会被破坏,因为它们在转换为基于二进制幂的数学运算时变成了无限序列,在二进制浮点数运算中,0.1 * 0.1 != 0.01,因为0.1无法被完全表示。数学运算也会导致漂移——对于美分和美元进行加减运算可能得到像0.9999999999999这样的数字。toString()方法通过四舍五入来隐藏这个问题,但确切的比较立即就出现了错误。 - David
显示剩余10条评论

206
我的问题是什么时候应该使用double类型,什么时候应该使用decimal类型?
当您处理范围在10^(+/-28)内的值并且您对基于十进制表示的行为有期望时,使用decimal类型 - 基本上是货币。
当您需要相对精度(即在大量不同数量级上失去尾数精度不是问题)时,请使用double类型 - double类型覆盖了10^(+/-300)以上。科学计算是最好的例子。
哪种类型适用于货币计算?
decimal类型,decimal类型,decimal类型
不接受替代品。
最重要的因素是,double类型作为二进制小数实现,根本无法准确表示许多十进制分数(如0.1),而且它的总位数较小,因为它是64位宽,而decimal类型则为128位宽。最后,金融应用程序通常必须遵循特定的rounding modes(有时由法律规定)。decimal类型支持这些;double类型不支持。

2
毫无疑问,double 不适用于表示金融价值,但是当你写到与 decimal 相比,double 不支持特定的舍入模式时,你确切指的是什么?据我所知,Math.Round 有多个重载方法,可以接受 MidpointRounding 参数,同时适用于 doubledecimal - vgru
2
@Groo:我猜我一定看了 .Net 1.1 API,这个方法是在2.0中添加的 - 但由于二进制小数的问题,它仍然有点无意义。当前API文档中有一个例子说明了这个问题。 - Michael Borgwardt
在许多比较中看到了这行代码,但是无法理解其含义。您能否详细说明一下呢?“Double不能准确地表示许多十进制小数(如0.1)”。 - Imad
1
@lmad:我有一个网站可以解释这个问题:https://floating-point-gui.de/ - 基本上,这就是为什么十进制数无法准确表示1/3的原因。 - Michael Borgwardt
3
当你说“decimal,decimaldecimal”时,我应该使用哪一个? - Shadi Alnamrouti

46
根据浮点类型的特征
.NET类型 C# 关键字 精度
System.Single float 约6-9位数
System.Double double 约15-17位数
System.Decimal decimal 28-29位数

我以前因为在处理大量数据时使用了错误的类型而受到过教训(好几年前):

  • £520,532.52 - 8位数
  • £1,323,523.12 - 9位数

对于float类型,最大只能处理到100万。

一个15位数的货币值:

  • £1,234,567,890,123.45

对于double类型,最大只能处理到9万亿。但是在除法和比较方面更加复杂(我绝对不是浮点数和无理数的专家 - 可以看看Marc的观点)。混合使用decimal和double会导致问题:

如果一个浮点数用于数学或比较运算,而使用十进制数则可能产生不同的结果,因为浮点数可能不准确。

精准逼近小数时,何时应该使用double而不是decimal?提供了一些类似且更深入的答案。在金融应用中使用double而不是decimal是一种微观优化-这是我看待它的最简单方式。

1
520,532.52 有8个有效数字,而 1,323,523.12 则有9个。参考链接:http://mathsfirst.massey.ac.nz/Algebra/Decimals/SigFig.htm - Royi Namir
1
你的帖子中的 floatdoubledecimal 链接已损坏。这里是关于这三种数值类型别名的最新 MSDN 文档链接:https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/floating-point-numeric-types - Mass Dot Net

43

Decimal用于精确值,Double用于近似值。

USD: $12,345.67 USD (Decimal)
CAD: $13,617.27 (Decimal)
Exchange Rate: 1.102932 (Double)

16
十进制浮点数不适用于精确值。根据文档,十进制浮点数提供28-29位小数的精度。十进制浮点数不执行分析算术,因此不是“精确”的。对于货币来说,十进制浮点数非常适用,因为即使价值达到数万亿美元,它仍然可以让您在累积算术误差中保留10位数字,同时仍然能够精确地舍入到美分。 - Triynko
7
为什么汇率是双倍而不是十进制?这难道不只是1美元兑加拿大元的价格吗? - gerrit
4
@gerrit 汇率不是1美元兑换多少加元的“价格”,而是两者价值的比率。根据您的信息来源,决定了您将获得多少小数位。例如,1美元价值1.0016加元,1英镑价值1.5909加元,1越南盾价值0.000048加元。它是一个比率,因此无法在任何地方截断而不失去精度。 - Ian Boyd
1
@gerrit 0.000048 是来自加拿大银行的数据。XE网站显示1越南盾等于0.0000478405加元。这些数值是通过除法计算得出的,因此结果为浮点数。 - Ian Boyd
不,十进制并不是精确的。对于上面的示例中的汇率,您应该使用十进制,因为输入和输出都是基于10的(当使用double时,在基数转换时会丢失精度,因为质因数分解中没有5)。 - user2622016
同样的,汇率也不是精确的倒数:通常在一个方向上兑换比另一个方向上获得“更少的钱”,因此如果你反复兑换,金额最终会降为零。这是由于两个汇率不同,而不是由于费用甚至取整。 - user4624979

28

对于货币来说,建议使用decimal类型。虽然这会占用更多内存,但不像double类型有时会遇到舍入问题。


15
它存在所有四舍五入的问题:尝试计算 1m/3m + 1m/3m == 2m/3m。主要区别在于——有效数字位数更多,最重要的是:当操作除数中带有5的数字时,不会出现精度损失。例如,1m/5m + 1m/5m 将完全等于 2m/5m - user2622016

9

一定要在你处理货币计算时使用整数类型。
这一点不能够强调得太多,因为乍一看似乎浮点类型就足够了。

以下是 Python 代码示例:

>>> amount = float(100.00) # one hundred dollars
>>> print amount
100.0
>>> new_amount = amount + 1
>>> print new_amount
101.0
>>> print new_amount - amount
>>> 1.0

看起来相当正常。

现在再试试用 10^20 津巴布韦元:

>>> amount = float(1e20)
>>> print amount
1e+20
>>> new_amount = amount + 1
>>> print new_amount
1e+20
>>> print new_amount-amount
0.0

正如您所看到的,美元符号已经消失了。

如果您使用整数类型,则可以正常工作:

>>> amount = int(1e20)
>>> print amount
100000000000000000000
>>> new_amount = amount + 1
>>> print new_amount
100000000000000000001
>>> print new_amount - amount
1

9
即使使用非常小/大的值,也可以发现双精度浮点数二进制近似和实际十进制值之间的差异,许多小值无法准确存储。计算“1-0.1-0.9”(确保编译器不优化此方程),并将其与零进行比较。您会发现使用双精度浮点数时,结果是约为2e-17而不是0(确保运行比较,因为许多打印/ToString函数会在一定数量的小数位数后舍入双精度浮点数以消除这些类型的错误)。 - David
3
整数?!当你有1.5美元时会发生什么? - Noctis
4
如果你认真考虑,@Noctis,你就会想出一个解决方案。 - Otto Allmendinger
1
有很多解决方案,但他在谈论double vs decimal,所以除非他完全错了,否则他需要小数部分...这就是为什么你的答案让我感到奇怪。 - Noctis
3
没有理由为了精度而使用 int 而不是 decimal(也许是出于性能原因)。避免使用 double,而是使用 decimal。Decimal 使用基数为 10 的指数,因此在解析类似于 0.1 的基数为 10 的值时,不会遇到与 double 相同的二进制舍入误差。 - BlueMonkMN
显示剩余5条评论

6

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