什么时候应该使用双精度代替十进制?


265

我可以列举使用double(或float)代替的三个优点decimal

  1. 使用更少的内存。
  2. 更快,因为处理器原生支持浮点数学运算。
  3. 可以代表更大范围的数字。

但是这些优点似乎仅适用于计算密集型操作,例如建模软件中的操作。当然,当需要精确度(例如财务计算)时,不应使用双精度。那么,有没有任何实际的理由选择double(或float)而不是decimal“常规”应用程序?

编辑添加:感谢所有出色的答复,我从中了解到。

还有一个问题:少数人指出,加倍数可以更精确地表示实数。当声明时,我认为它们通常也可以更准确地表示它们。但是,在执行浮点运算时精度是否会降低(有时会显着降低),这是不正确的说法?



5
这很经常受到支持,但我仍然为此感到困惑。例如,我正在开发一个进行财务计算的应用程序,因此我始终使用十进制。但是Math和VisualBasic.Financial函数使用double,因此有很多转换让我不断地猜测十进制的使用。
Jamie Ide 2014年

@JamieIde疯狂的是,财务功能使用双精度,货币应始终为小数。
克里斯·马里西奇

@ChrisMarisic但是杰米·艾德(Jamie Ide)使用double可以处理遗留垃圾吗?然后,您也应该使用double,否则许多转换将导致舍入错误...难怪他提到VisualBasic pfffhh ....
Elisabeth,

@伊丽莎白,我可能会使用正确支持十进制的其他库。无论VisualBasic.Financial提供什么样的功能,今天其他多个图书馆中都可能存在
Chris Marisic

Answers:


306

我认为您已经很好地总结了优点。但是,您缺少一点。该decimal类型仅能更准确地表示以10基数的数字(例如,用于货币/金融计算的数字)。通常,该 double类型将至少提供相同的精度(如果我输入错误,请纠正我),并且为任意实数提供更快的速度。一个简单的结论是:在考虑使用哪个时,double除非您需要提供的base 10准确性,否则请始终使用decimal

编辑:

关于操作后浮点数精度降低的其他问题,这是一个更微妙的问题。确实,执行每个操作后,精度(在这里我将术语互换使用以表示精度)将稳步下降。这是由于两个原因:

  1. 某些数字(最明显的是小数)不能真正以浮点数形式表示的事实
  2. 发生舍入错误,就好像您是手动进行计算一样。这些错误是否足够重要以至于值得我们深思,这在很大程度上取决于上下文(您正在执行多少操作)。

在所有情况下,如果要比较两个在理论上应该等效的浮点数(但是使用不同的计算得出的),则需要允许一定程度的公差(相差多少,但通常很小) 。

有关可以引入准确性错误的特殊情况的更详细概述,请参阅Wikipedia文章的Accuracy部分。最后,如果您想在机器级别上对浮点数/运算进行认真的深入(和数学)讨论,请尝试阅读常被引用的文章《每个计算机科学家应该了解的浮点算术》


1
您能否提供一个以10为底的数字的示例,当转换为以2为底的数字时会失去精度?
Mark Cidade

@Mark:至少根据Jon Skeet的说法,1.000001是一个示例。(请参阅此页面的问题3:yoda.arachsys.com/csharp/teasers-answers.html
Noldorin

25
@Mark:非常简单的示例:0.1是以2为底的周期分数,因此无法精确地用a表示double。现代计算机仍将打印正确的值,只是因为它们会“猜测”结果,而不是因为它的表达方式是正确的。
Konrad Rudolph

1
Decimal类型的尾数精度为93位,而的精度为52位double。我希望微软支持IEEE 80位格式,即使必须将其填充为16个字节也是如此。它将允许更大的范围,double或者Decimal比其更好的速度Decimal,支持先验运算(例如sin(x),log(x)等),以及精度,虽然Decimal不如更好double
2013年

@charlotte:如果您阅读了我的完整文章,就会看到解释了。
Noldorin

59

您似乎发现使用浮点类型的好处。我倾向于在所有情况下都针对小数进行设计,并依靠探查器让我知道对小数的操作是否会导致瓶颈或速度下降。在这些情况下,我将“下转换”为两倍或浮点数,但只能在内部进行,并尝试通过限制所执行的数学运算中的有效位数来管理精度损失。

通常,如果您的值是瞬时值(不可重用),则可以安全地使用浮点类型。浮点类型的真正问题是以下三种情况。

  1. 您正在汇总浮点值(在这种情况下,精度误差会增加)
  2. 您基于浮点值构建值(例如,在递归算法中)
  3. 您正在使用很多有效数字进行数学运算(例如, 123456789.1 * .000000000000000987654321

编辑

根据有关C#小数参考文档

所述的十进制关键字表示128位的数据类型。与浮点类型相比,十进制类型具有更高的精度和更小的范围,这使其适用于财务和货币计算。

因此,请澄清我的上述陈述:

我倾向于在所有情况下都针对小数进行设计,并依靠探查器让我知道对小数的操作是否会导致瓶颈或速度下降。

我只在小数点有利的行业工作过。如果您使用的是物理或图形引擎,则设计浮点类型(float或double)可能会更有益。

十进制不是无限精确的(不可能为原始数据类型中的非整数表示无限精确度),但是它比double精确得多:

  • 十进制= 28-29个有效数字
  • 两倍= 15-16个有效数字
  • float = 7个有效数字

编辑2

在回应Konrad Rudolph的评论时,上面的项目1绝对正确。不精确性的集合确实的确很复杂。请参见下面的代码作为示例:

private const float THREE_FIFTHS = 3f / 5f;
private const int ONE_MILLION = 1000000;

public static void Main(string[] args)
{
    Console.WriteLine("Three Fifths: {0}", THREE_FIFTHS.ToString("F10"));
    float asSingle = 0f;
    double asDouble = 0d;
    decimal asDecimal = 0M;

    for (int i = 0; i < ONE_MILLION; i++)
    {
        asSingle += THREE_FIFTHS;
        asDouble += THREE_FIFTHS;
        asDecimal += (decimal) THREE_FIFTHS;
    }
    Console.WriteLine("Six Hundred Thousand: {0:F10}", THREE_FIFTHS * ONE_MILLION);
    Console.WriteLine("Single: {0}", asSingle.ToString("F10"));
    Console.WriteLine("Double: {0}", asDouble.ToString("F10"));
    Console.WriteLine("Decimal: {0}", asDecimal.ToString("F10"));
    Console.ReadLine();
}

输出以下内容:

Three Fifths: 0.6000000000
Six Hundred Thousand: 600000.0000000000
Single: 599093.4000000000
Double: 599999.9999886850
Decimal: 600000.0000000000

如您所见,即使我们从相同的源常量中进行相加,double的结果也不太精确(尽管可能会正确舍入),float的精确度也很差,以至于其减小到仅两个有效数字。


1
点1不正确。精度/舍入误差仅发生在铸造中,而不发生在计算中。这当然正确,大部分的数学运算是不稳定的,因此乘以错误。但这是另一个问题,它适用于精度有限的所有数据类型,尤其是十进制。
康拉德·鲁道夫

1
@Konrad Rudolph,请参见“编辑2”中的示例,作为我试图在项目1中提出的要点的证据。通常,此问题不会显现出来,因为正的不精确度与负的不精确度是平衡的,它们会冲刷掉聚合,但聚合相同的数字(如我在示例中所做的)突出显示了问题。
Michael Meadows,2009年

很好的例子。刚向我的初级开发人员展示了它,孩子们对此感到惊讶。
马查多,2012年

现在,您可以用2 / 3rds而不是3 / 5ths做同样的事情...您应该了解处理2 / 3rds的十六进制数字系统。
gnasher729

1
@ gnasher729,使用2 / 3rds而不是3 / 5ths不能很好地处理不同的类型。有趣的是,浮点值产生了, Single: 667660.400000000000而十进制值产生了Decimal: 666666.7000000000。浮点值比正确值小一千。
jhenninger 2015年

25

如其他建议那样,对以10为基数的值使用十进制,例如财务计算。

但是对于任意计算值,double通常更为准确。

例如,如果您要计算投资组合中每条线的权重,请使用double,因为结果将更接近100%。

在以下示例中,doubleResult比decimalResult更接近1:

// Add one third + one third + one third with decimal
decimal decimalValue = 1M / 3M;
decimal decimalResult = decimalValue + decimalValue + decimalValue;
// Add one third + one third + one third with double
double doubleValue = 1D / 3D;
double doubleResult = doubleValue + doubleValue + doubleValue;

因此,再次以投资组合为例:

  • 投资组合中每条线的市场价值都是货币价值,可能最好用十进制表示。

  • 投资组合中每条线的权重(=市场价值/ SUM(市场价值))通常更好地表示为两倍。


6

当您不需要精度时,请使用double或float,例如,在我编写的平台游戏中,我使用float来存储玩家的速度。显然,这里我不需要超高精度,因为我最终舍入为一个Int以便在屏幕上绘图。


3
精度是小数的唯一优势,这是正确的。您不应该问何时应该使用浮点数而不是小数点。那应该是你的第一个想法。然后的问题是,什么时候应该使用小数点(答案就在这里……当精度很重要时)。
个体猎人

3
@Daniel Straight,这很有趣,但是我有相反的意见。我认为使用不太精确的类型是因为它的性能特征等于对其进行了预优化。您可能需要多次为该预优化支付费用,然后才能意识到其好处。
迈克尔·梅多斯

3
@Michael Meadows,我可以理解这个论点。但是要注意的一点是,过早优化的主要抱怨之一是程序员不会倾向于知道什么会变慢。我们毫无疑问地知道,小数比双精度慢。不过,我想在大多数情况下,用户无论如何都不会注意到性能的提高。当然,在大多数情况下,也不需要精度。嘿。
实例猎人

实际上,十进制浮点数比使用相同位数的二进制浮点数少。Decimal的优点是能够精确表示金融计算中常见的DECIMAL分数(如0.01)。
2010年

好吧,这不是很正确:)-在许多游戏中,浮点数可能并不理想,因为它们不一致。看到这里
BlueRaja-Danny Pflughoeft

4

在某些会计中,请考虑是否可以使用整数类型来代替或结合使用整数类型。例如,假设您使用的规则要求每个计算结果都保留至少6个小数位,并且最终结果将四舍五入到最接近的美分。

计算$ 100的1/6会得到$ 16.66666666666666 ...,因此在工作表中执行的值将是$ 16.666667。双精度和十进制都应将结果精确地输出到6个小数位。但是,我们可以通过将结果作为整数16666667进行结转来避免任何累积误差。每个后续计算都可以以相同的精度进行,并以类似的方式结转。继续该示例,我计算了该金额的德克萨斯州销售税(16666667 * .0825 = 1375000)。将两者相加(这是一个简短的工作表)1666667 + 1375000 =18041667。将小数点移回给我们,即得出18.041667,即$ 18.04。

虽然这个简短的示例不会使用双精度或十进制产生累积误差,但是很容易显示仅计算双精度或十进制并结转会累积大量误差的情况。如果您使用的规则要求有限的小数位数,则将每个值存储为整数,方法是乘以10 ^(小数位数),然后除以10 ^(小数位数)以获取实际值值将避免任何累积错误。

在没有几分钱的情况下(例如,自动售货机),根本没有理由使用非整数类型。简单地将其视为数便士,而不是美元。我见过的代码中,每次计算仅涉及整个便士,但是使用double会导致错误!仅整数运算消除了该问题。因此,我的非常规答案是在可能的情况下放弃双精度和十进制。


3

如果需要与其他语言或平台进行二元插入,则可能需要使用标准化的float或double。


2

注意:这篇文章基于http://csharpindepth.com/Articles/General/Decimal.aspx中十进制类型的功能信息以及我对此的解释。我将假设Double是正常的IEEE双精度。

注意2:此帖子中最小和最大代表数字的大小。

“十进制”的优点。

  • “十进制”可以精确表示可以写为(足够短)十进制分数的数字,而double则不能。这在财务分类帐中很重要,在类似情况下,结果必须与执行计算的人员所匹配的内容非常重要。
  • “十进制”的尾数比“双”大。这意味着对于归一化范围内的值,“十进制”将具有比double更高的精度。

小数的缺点

  • 它将慢得多(我没有基准,但我想至少可能要高一个数量级),十进制不会从任何硬件加速中受益,并且算术运算将需要相对昂贵的乘/除以10的幂(这比2)的乘除运算要昂贵得多,要在加法/减法之前匹配指数并在乘/除后使指数重新回到范围内,这比2)的幂要昂贵得多。
  • 小数点将溢出较早的两倍。十进制只能代表不超过±2 96 -1的数字。通过比较,double最多可以表示±2的数字 1024
  • 小数点将较早下溢。用十进制表示的最小数字为±10 -28。通过比较,如果支持亚分子编号,则double可以表示的值低至2 -149(大约10 -45),而2 -126(大约10 -38))的值。
  • 小数占用两倍的内存。

我的意见是,对于理财工作以及在其他情况下,精确匹配人工计算非常重要,并且在其余时间中,应使用double作为默认选择,您应该默认使用“十进制”。


2

取决于您需要什么。

因为float和double是二进制数据类型,所以您在舍入数字时会遇到一些困难和错误,例如double会将0.1舍入为0.100000001490116,double还将1/3舍入为0.33333334326441。简而言之,并非所有实数都具有双精度类型的准确表示

幸运的是,C#还支持所谓的十进制浮点运算,其中数字是通过十进制数字系统而不是二进制系统表示的。因此,在存储和处理浮点数时,十进制浮点算法不会失去准确性。这使其非常适合需要高精度的计算。



0

选择您的应用程序的功能类型。如果您需要像财务分析那样的准确性,那么您已经回答了您的问题。但是,如果您的应用程序可以以估计的价格解决,那么您可以加倍。

您的应用程序是否需要快速计算,还是他会在世界各地一直为您提供答案?这实际上取决于应用程序的类型。

图形饿了吗?浮动或两倍就足够了。财务数据分析,流星达到了什么样的精度?那些需要一点精度:)


8
小数也是估计数。它们符合金融算术的惯例,但是在涉及物理的计算中没有优势。
David Thornley,2009年

0

十进制具有更宽的字节,CPU本身支持double。十进制是10的基数,因此在计算十进制时会发生十进制到双精度的转换。

For accounting - decimal
For finance - double
For heavy computation - double

请记住,.NET CLR仅支持Math.Pow(double,double)。不支持十进制。

.NET Framework 4

[SecuritySafeCritical]
public static extern double Pow(double x, double y);

0

如果双精度值短于十进制显示,则默认情况下,双精度值将序列化为科学计数法。(例如.00000003将是3e-8)十进制值将永远不会序列化为科学计数法。当序列化供外部方使用时,这可能是一个考虑因素。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.