关于c#:我什么时候应该用double而不是decimal?

When should I use double instead of decimal?

我可以列举使用doublefloat而不是decimal的三个优点:

  • 使用更少的内存。
  • 更快,因为处理器本身支持浮点数学操作。
  • 可以表示更大范围的数字。
  • 但这些优势似乎只适用于计算密集型操作,如建模软件中的操作。当然,如果需要精确性,例如财务计算,则不应使用双精度。那么,在"正常"应用中,是否有任何实际的理由选择EDOCX1(或float)而不是decimal)?

    编辑添加:感谢所有的伟大反应,我从他们身上学到了很多。

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


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

    编辑:

    关于您关于操作后浮点数字精度降低的其他问题,这是一个稍微微妙的问题。实际上,每次操作完成后,精度(这里我使用可互换的术语来表示精度)都会稳步下降。这是由于两个原因:

  • 某些数字(最明显的是小数)不能以浮点形式真正表示。
  • 舍入错误会发生,就像您手工计算一样。这在很大程度上取决于上下文(您正在执行的操作数),这些错误是否重要到足以引起人们的深思熟虑。
  • 在所有情况下,如果要比较理论上应相等的两个浮点数(但使用不同的计算得出),则需要允许一定程度的公差(变化幅度很大,但通常非常小)。

    有关可以引入精确度错误的特定情况的更详细概述,请参阅维基百科文章的准确性部分。最后,如果你想认真深入(和数学)讨论机器级的浮点数/运算,试着读一篇经常被引用的文章,每个计算机科学家都应该知道关于浮点运算的知识。


    使用浮点类型的好处似乎很明显。在所有情况下,我都倾向于设计小数,并且依赖于一个分析器来告诉我十进制操作是否会导致瓶颈或减速。在这些情况下,我将"向下强制转换"为双精度或浮点型,但只在内部进行,并通过限制正在执行的数学运算中的有效位数来小心地尝试管理精度损失。

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

  • 您正在聚合浮点值(在这种情况下,精度错误复合)
  • 基于浮点值构建值(例如在递归算法中)
  • 您正在用非常多的有效数字进行数学运算(例如,123456789.1 * .000000000000000987654321)
  • 编辑

    根据C小数的参考文件:

    The decimal keyword denotes a
    128-bit data type. Compared to
    floating-point types, the decimal type
    has a greater precision and a smaller
    range, which makes it suitable for
    financial and monetary calculations.

    为了澄清我的上述声明:

    I tend to design for decimals in all
    cases, and rely on a profiler to let
    me know if operations on decimal is
    causing bottlenecks or slow-downs.

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

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

    • 十进制=28-29个有效数字
    • 双精度=15-16个有效数字
    • 浮点=7个有效数字

    编辑2

    作为对康拉德·鲁道夫评论的回应,上述第1项肯定是正确的。不精确的集合确实是复合的。请参见下面的代码以获取示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    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();
    }

    输出如下:

    1
    2
    3
    4
    5
    Three Fifths: 0.6000000000
    Six Hundred Thousand: 600000.0000000000
    Single: 599093.4000000000
    Double: 599999.9999886850
    Decimal: 600000.0000000000

    正如您所看到的,即使我们从同一个源常量添加,double的结果也不那么精确(尽管可能会正确舍入),而float的精度也要低得多,直到它被减少到只有两个有效数字为止。


    如其他人所建议的,使用十进制作为基数10的值,例如财务计算。

    但对于任意计算值,double通常更精确。

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

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

    1
    2
    3
    4
    5
    6
    // 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;

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

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

    • 投资组合中每一行的权重(=市场价值/总和(市场价值))通常最好表示为双倍。


    当你不需要精确的时候,可以使用双精度或浮点数,例如,在我写的platformer游戏中,我使用浮点数来存储玩家的速度。很明显,我不需要超精确,因为我最终会取整到一个int来在屏幕上画图。


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

    100美元的1/6计算得出16.666666666666美元……,因此工作表中的值将为16.666667美元。double和decimal都应该精确到6位小数。然而,我们可以通过将结果作为一个整数16666667进行结转来避免任何累积误差。每一个后续计算都可以以相同的精度进行,并以相同的方式进行。继续这个例子,我计算该金额的德州销售税(166667*.0825=1375000)。加上这两个(这是一个简短的工作表)1666667+1375000=18041667。把小数点移回去,我们得到18.041667美元,或者18.04美元。

    虽然这个简短的示例不会使用double或decimal产生累积错误,但很容易显示只计算double或decimal并进行结转会累积显著错误的情况。如果您在下面操作的规则需要有限的小数位数,则将每个值存储为一个整数,乘以10^(小数位数的要求值),然后除以10^(小数位数的要求值),得到实际值,将避免任何累积错误。

    如果分币没有出现(例如自动售货机),就没有理由使用非整数类型。简单地把它看成是计算硬币,而不是美元。我见过代码,每个计算只涉及整分钱,但使用双精度会导致错误!仅整数数学删除了该问题。所以我的非传统答案是,如果可能的话,放弃双精度和十进制。


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


    注:本文基于http://csharpindepth.com/articles/general/decimal.aspx中decimal类型的功能信息以及我自己对其含义的解释。我假设双精度是标准的IEEE双精度。

    注2:本帖中的最小值和最大值参考数字的大小。

    "十进制"的优点。

    • "decimal"可以精确地表示可以写成(足够短)小数的数字,double不能。这在财务分类账中很重要,同样重要的是,结果必须与进行计算的人给出的结果完全匹配。
    • "decimal"的尾数比"double"大得多。这意味着对于标准化范围内的值,"十进制"的精度要比"双精度"高得多。

    十进制不等式

    • 它将慢得多(我没有基准,但我猜至少一个数量级可能更高),十进制将不会从任何硬件加速中受益,在它上面的算术将需要相对昂贵的乘/除10次方的幂(比乘和除2次方的幂贵得多)来匹配t在加/减之前进行指数化,并在乘/除之后将指数恢复到范围内。
    • 小数会比双精度早点溢出。小数只能表示小于等于±;296-1的数字。通过比较,double可以表示接近±;21024的数字。
    • 十进制将提前下溢。以十进制表示的最小数字是±;10-28。通过比较,如果支持子域数,double可以表示小于2-149(约10-45)的值;如果不支持子域数,double可以表示小于2-126(约10-38)的值。
    • 十进制占用的内存是double的两倍。

    我的意见是,你应该默认使用"十进制"的货币工作和其他情况下,匹配人类计算是非常重要的,你应该使用双精度作为你的默认选择剩余的时间。


    取决于你需要它做什么。

    因为float和double是二进制数据类型,所以在舍入数中有一些diifculties和errror,例如double将舍入0.1到0.10000001490116,double也将舍入1/3到0.333334326441。简单地说,不是所有的实数都有精确的双类型表示。

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


    如果一个双精度值的符号短于十进制显示,那么默认情况下,该值将序列化为科学符号。(例如,0000000 3将是3e-8)十进制值永远不会序列化为科学记数法。当对外方进行消费系列化时,这可能是一个考虑因素。


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

    1
    2
    3
    For accounting - decimal
    For finance - double
    For heavy computation - double

    记住.NET CLR只支持math.pow(double,double)。不支持十进制。

    .NET框架4

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

    选择应用程序的"输入"函数。如果你需要像财务分析那样的精确性,你已经回答了你的问题。但如果你的申请能以一个估计值来解决,你可以加倍。

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

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


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