什么时候应该使用double而不是decimal?

时间:2009-04-29 16:38:22

标签: c# types floating-point double decimal

使用double(或float)代替decimal,我可以说出三个好处:

  1. 使用较少的内存。
  2. 更快,因为处理器本身支持浮点数学运算。
  3. 可以代表更大范围的数字。
  4. 但这些优势似乎只适用于计算密集型操作,例如建模软件中的操作。当然,在需要精确度时,不应使用双精度数,例如财务计算。那么在“普通”应用程序中选择double(或float)而不是decimal是否有任何实际理由?

    编辑添加: 感谢所有伟大的回应,我向他们学习。

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

12 个答案:

答案 0 :(得分:296)

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

修改

关于您在操作后降低浮点数准确性的其他问题,这是一个稍微微妙的问题。实际上,在执行每个操作之后,精度(我在这里可互换地使用术语)将稳定地减少。这是由于两个原因:

  1. 某些数字(最明显的小数)无法以浮点形式真实表示
  2. 发生舍入错误,就像您手动执行计算一样。它在很大程度上取决于上下文(您正在执行多少操作)这些错误是否足够重要,无论如何都要多加考虑。
  3. 在所有情况下,如果你想比较理论上应该相等的两个浮点数(但是使用不同的计算得出),你需要允许一定程度的容差(多少变化,但通常是很小)。

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

答案 1 :(得分:56)

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

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

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

    根据reference documentation on C# decimals

      

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

    所以澄清我的上述陈述:

      

    我倾向于设计所有的小数   案例,并依靠分析器让   我知道小数运算是否正确   导致瓶颈或减速。

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

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

    • decimal = 28-29有效数字
    • double = 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的精确度要差得多,直到它已经到了减少到只有两位有效数字。

答案 2 :(得分:24)

对基数为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(市场价值))通常更好地表示为双倍。

答案 3 :(得分:6)

当你不需要精确度时,使用double或float,例如,在我写的平台游戏中,我使用浮动来存储玩家的速度。显然我在这里不需要超精确,因为我最终会转向Int以在屏幕上绘图。

答案 4 :(得分:4)

在某些会计中,请考虑使用整数类型或结合使用整数类型的可能性。例如,假设你操作的规则要求每个计算结果至少带有6个小数位,最终结果将四舍五入到最接近的便士。

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

虽然这个简短的例子不会产生使用double或decimal的累积误差,但是很容易显示简单地计算double或decimal并且继续前进会累积重大错误的情况。如果您在其下运行的规则需要有限的小数位数,则将每个值存储为整数乘以10 ^(所需的小数位数),然后除以10 ^(所需的小数位数)以获得实际值值将避免任何累积错误。

在没有发生几分钱的情况下(例如,自动售货机),根本没有理由使用非整数类型。简单地把它想象成便士,而不是美元。我看过代码,其中每个计算只涉及整个便士,但使用double会导致错误!仅整数数学删除了该问题。所以我的非常规答案是,如果可能的话,放弃双倍和小数。

答案 5 :(得分:3)

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

答案 6 :(得分:2)

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

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

“十进制”的优点。

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

十进制的缺点

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

我的意见是你应该默认使用“十进制”进行金钱工作以及其他匹配人类计算非常重要的情况,并且你应该在其余时间使用double作为默认选择。

答案 7 :(得分:0)

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

答案 8 :(得分:0)

选择应用程序的功能类型。如果您需要财务分析中的精确度,您已回答了您的问题。但是,如果你的申请可以通过双倍估算你的确定。

您的申请是否需要快速计算,或者他是否会一直在世界上给您答案?这实际上取决于应用程序的类型。

图形饥饿?漂浮或双倍就足够了。财务数据分析,流星撞击行星的那种精确度?那些需要一点精度:)

答案 9 :(得分:0)

Decimal有更宽的字节,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);

答案 10 :(得分:0)

如果该表示法比十进制显示短,则双值将默认序列化为科学计数法。 (例如.00000003将是3e-8)十进制值永远不会序列化为科学记数法。当序列化供外部消费时,这可能是一个考虑因素。

答案 11 :(得分:0)

取决于您的需要。

因为float和double是二进制数据类型,所以在轮数中你有 一些 diifculties和errrors,所以例如double将会舍入0.1到0.100000001490116,double会也是1/3至0.33333334326441。简单地说,并非所有实数都具有双重类型的准确表示

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