【问题标题】:When should I use double instead of decimal?我什么时候应该使用双精度而不是十进制?
【发布时间】:2010-10-22 15:20:55
【问题描述】:

我可以说出使用double(或float)而不是decimal 的三个优点:

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

但这些优势似乎只适用于计算密集型操作,例如建模软件中的操作。当然,当需要精度时,不应该使用双精度数,例如财务计算。那么在“普通”应用程序中选择double(或float)而不是decimal 有什么实际理由吗?

编辑添加: 感谢大家的好评,我向他们学习了。

还有一个问题:一些人指出双精度数可以更精确地表示实数。当声明时,我认为它们通常也更准确地代表它们。但是,当执行浮点运算时,准确度可能会降低(有时会显着降低),这是真的吗?

【问题讨论】:

  • 这经常被投票,我仍然在努力。例如,我正在开发一个进行财务计算的应用程序,因此我始终使用小数。但是 Math 和 VisualBasic.Financial 函数使用 double 所以有很多转换让我不断地猜测小数的使用。
  • @JamieIde 太疯狂了,财务函数使用双精度,货币应始终为十进制。
  • @ChrisMarisic 但是jamie Ide 可以用double 处理遗留的废话做什么?那么你也应该使用 double 否则许多转换会导致舍入错误......难怪他提到 VisualBasic pfffhh......
  • @Elisabeth 我可能会使用正确支持十进制的不同库。 VisualBasic.Financial 提供的任何东西都可能存在于当今的多个其他库中

标签: c# types floating-point double decimal


【解决方案1】:

我认为您已经很好地总结了这些优势。但是,您错过了一点。 decimal 类型仅在表示 base 10 数字时更准确(例如,用于货币/金融计算的数字)。一般来说,double 类型至少会提供同样高的精度(如果我错了,请有人纠正我),并且对于任意实数肯定会更快。简单的结论是:在考虑使用哪个时,始终使用double,除非您需要decimal 提供的base 10 精度。

编辑:

关于您关于运算后浮点数准确性降低的其他问题,这是一个稍微微妙的问题。事实上,每次操作执行后,精度(我在这里可以互换地使用该术语来表示精度)会稳步下降。这是由于两个原因:

  1. 某些数字(最明显的是小数)无法真正以浮点形式表示这一事实
  2. 出现舍入错误,就像您手动进行计算一样。然而,这些错误是否严重到值得深思熟虑的程度在很大程度上取决于上下文(您正在执行多少操作)。

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

有关可能引入精度错误的特定情况的更详细概述,请参阅Wikipedia article 的精度部分。最后,如果您想在机器级别对浮点数/运算进行认真深入(和数学)的讨论,请尝试阅读经常被引用的文章 What Every Computer Scientist Should Know About Floating-Point Arithmetic

【讨论】:

  • 您能否提供一个以 10 为底的数字的示例,当转换为以 2 为底时,精度会丢失?
  • @Mark: 1.000001 就是一个例子,至少根据 Jon Skeet 的说法。 (见本页问题3:yoda.arachsys.com/csharp/teasers-answers.html
  • @Mark:非常简单的例子:0.1 是一个以 2 为底的周期分数,因此它不能在 double 中精确表示。现代计算机仍然会打印正确的值,但这只是因为它们“猜测”了结果,而不是因为它确实被正确表达了。
  • Decimal 类型的尾数精度为 93 位,而 double 的精度约为 52 位。不过,我希望 Microsoft 支持 IEEE 80 位格式,即使它必须填充到 16 个字节;它允许比doubleDecimal 更大的范围,比Decimal 更好的速度,支持超越运算(例如sin(x),log(x)等),以及虽然不完全的精度和Decimal 一样好会比double 好得多。
  • @charlotte:如果您阅读了我的完整帖子,您会看到已经解释过了。
【解决方案2】:

您似乎很了解使用浮点类型的好处。我倾向于在所有情况下都设计小数,并依靠分析器让我知道小数操作是否会导致瓶颈或减速。在这些情况下,我将“向下转换”为 double 或 float,但仅在内部进行,并通过限制正在执行的数学运算中的有效数字的数量来谨慎地尝试管理精度损失。

一般来说,如果您的值是瞬态的(未重用),那么您可以安全地使用浮点类型。浮点类型的真正问题在于以下三种情况。

  1. 您正在聚合浮点值(在这种情况下,精度误差复合)
  2. 您基于浮点值构建值(例如在递归算法中)
  3. 您正在使用大量有效数字进行数学运算(例如,123456789.1 * .000000000000000987654321

编辑

根据reference documentation on C# decimals

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

所以为了澄清我上面的陈述:

我倾向于设计小数 案例,并依靠探查器让 我知道十进制运算是否是 造成瓶颈或减速。

我只在喜欢小数的行业工作过。如果您正在研究物理或图形引擎,那么设计浮点类型(float 或 double)可能更有益。

Decimal 不是无限精确的(对于原始数据类型中的非整数,不可能表示无限精确),但它比 double 精确得多:

  • 十进制 = 28-29 位有效数字
  • double = 15-16 位有效数字
  • 浮点数 = 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 点不正确。精度/舍入误差只发生在铸造中,而不是计算中。大多数数学运算都是不稳定的,因此当然是正确的,因此会成倍增加误差。但这是另一个问题,它适用于所有精度有限的数据类型,尤其是十进制。
  • @Konrad Rudolph,请参阅“编辑 2”中的示例,作为我在第 1 项中试图提出的观点的证据。通常,这个问题不会表现出来,因为积极的不精确性与负面的不精确性,它们在聚合中洗涤,但聚合相同的数字(就像我在示例中所做的那样)突出了问题。
  • 很好的例子。刚把它展示给我的初级开发人员,孩子们都惊呆了。
  • 现在你可以用 2/3 代替 3/5 做同样的事情吗...你应该了解可以完美处理 2/3 的六十进制数系统。
  • @gnasher729,对于不同的类型,使用 2/3 代替 3/5 没有处理完美。有趣的是,浮点值产生Single: 667660.400000000000,而十进制值产生Decimal: 666666.7000000000。浮点值比正确值略小于一千。
【解决方案3】:

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

但对于任意计算值,双精度通常更准确。

例如,如果您想计算投资组合中每一行的权重,请使用 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;

所以再次以投资组合为例:

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

  • 投资组合中每一行的权重(= Market Value / SUM(Market Value))通常用双倍表示会更好。

【讨论】:

    【解决方案4】:

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

    【讨论】:

    • 精度是小数的唯一优势,这是正确的。您不应该问何时应该使用浮点数而不是小数。这应该是你的第一个想法。那么问题是什么时候应该使用小数(答案就在这里......当精度很重要时)。
    • @Daniel Straight,这很有趣,但我有相反的意见。我认为由于其性能特征而使用不太精确的类型相当于预优化。在您意识到它的好处之前,您可能需要多次为该预优化付费。
    • @Michael Meadows,我可以理解这个论点。不过需要注意的是,过早优化的主要抱怨之一是程序员往往不知道什么会变慢。但是,我们毫无疑问地知道小数比双精度数要慢。尽管如此,我想在大多数情况下,无论如何用户都不会注意到性能改进。当然,在大多数情况下,也不需要精度。呵呵。
    • 十进制浮点实际上不如使用相同位数的二进制浮点精确。 Decimal 的优势在于能够准确表示像 0.01 这样的 DECIMAL 分数,这在财务计算中很常见。
    • 嗯,这不太正确 :) - 在许多游戏中,浮点数可能是不可取的,因为它们不一致。见here
    【解决方案5】:

    在某些会计中,考虑使用整数类型代替或结合使用的可能性。例如,假设您操作的规则要求每个计算结果至少保留 6 位小数,最终结果将四舍五入到最接近的一分钱。

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

    虽然这个简短的示例不会产生使用双精度或小数的累积误差,但很容易展示简单地计算双精度或小数并结转会累积显着误差的情况。如果您操作的规则要求小数位数有限,则将每个值存储为整数,乘以 10^(所需的小数位数),然后除以 10^(所需的小数位数)得到实际值将避免任何累积错误。

    在不出现小数便士的情况下(例如,自动售货机),根本没有理由使用非整数类型。只需将其视为数便士,而不是美元。我看过代码,其中每个计算只涉及整便士,但使用 double 会导致错误!仅整数数学消除了该问题。所以我非常规的答案是,如果可能的话,放弃双精度和小数。

    【讨论】:

      【解决方案6】:

      如果您需要与其他语言或平台进行二进制互操作,那么您可能需要使用标准化的 float 或 double。

      【讨论】:

        【解决方案7】:

        取决于你需要它。

        因为 float 和 double 是二进制数据类型,所以在轮数中存在 一些 困难和错误,例如 double 会将 0.1 舍入到 0.100000001490116,double 会也将 1 / 3 舍入为 0.33333334326441。简单地说,并不是所有的实数在双精度类型中都有准确的表示

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

        【讨论】:

          【解决方案8】:

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

          注2:本文中最小和最大指的是数字的大小。

          “十进制”的优点。

          • “十进制”可以精确地表示可以写为(足够短的)十进制分数的数字,而双精度则不能。这在财务分类账和类似情况中很重要,在这些分类账中,结果与人类进行计算所给出的结果完全匹配很重要。
          • “decimal”的尾数比“double”大得多。这意味着对于其标准化范围内的值,“十进制”将具有比双精度高得多的精度。

          小数的缺点

          • 它会慢得多(我没有基准测试,但我猜至少一个数量级可能更多),十进制不会从任何硬件加速中受益,并且它的算术将需要相对昂贵的乘法/除法10(这比乘法和除以 2 的幂)要在加法/减法之前匹配指数并在乘法/除法之后将指数带回到范围内。
          • 十进制将比双精度更早溢出。十进制只能表示最大为 ±296-1 的数字。相比之下,double 可以表示接近 ±21024 的数字
          • 十进制将提前下溢。可以用十进制表示的最小数字是 ±10-28 。通过比较 double 可以表示低至 2-149(大约 10-45)的值,如果支持 subnromal 数字和 2-126(大约 10 -38) 如果不是。
          • decimal 占用的内存是 double 的两倍。

          我的意见是,对于金钱工作和其他需要精确匹配人工计算的情况,您应该默认使用“十进制”,并且您应该在其余时间使用使用 double 作为默认选择。

          【讨论】:

            【解决方案9】:

            如果您重视性能而不是正确性,请使用浮点数。

            【讨论】:

            • 十进制数字并不更正确,除非在某些有限的情况下有时(并非总是)很重要。
            【解决方案10】:

            选择应用程序的功能类型。如果您需要财务分析中的精确度,那么您已经回答了您的问题。但是,如果您的应用程序可以通过估计来解决,那么您可以加倍。

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

            图形饥饿? float 或 double 就足够了。金融数据分析,流星撞击行星的精度?那些需要一点精度:)

            【讨论】:

            • 十进制数也是估计值。它们符合金融算术的惯例,但在涉及物理的计算中没有优势。
            【解决方案11】:

            Decimal 具有更宽的字节,CPU 原生支持 double。十进制是以 10 为底的,因此在计算小数时会发生十进制到双精度的转换。

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

            请记住,.NET CLR 仅支持 Math.Pow(double,double)。不支持小数。

            .NET 框架 4

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

            【讨论】:

              【解决方案12】:

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

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2016-10-15
                • 1970-01-01
                • 2016-07-31
                • 1970-01-01
                • 2021-12-31
                • 2016-08-09
                • 2011-12-26
                • 1970-01-01
                相关资源
                最近更新 更多