285

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

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

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

编辑补充:感谢所有伟大的回应,我从他们那里学到了。

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

4

12 回答 12

316

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

编辑:

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

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

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

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

于 2009-04-29T16:46:19.633 回答
62

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

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

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

编辑

根据C# decimals 的参考文档

decimal关键字表示 128 位数据类型。与浮点类型相比,十进制类型的精度更高,范围更小,适用于金融和货币计算。

因此,为了澄清我的上述陈述:

我倾向于在所有情况下都设计小数,并依靠分析器让我知道小数操作是否会导致瓶颈或减速。

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

Decimal 不是无限精确的(不可能在原始数据类型中表示非整数的无限精度),但它比 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 的精确度要低得多,以至于它已经减少到只有两位有效数字。

于 2009-04-29T16:44:37.437 回答
26

正如其他人所建议的那样,对以 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;

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

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

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

于 2009-04-29T17:26:43.390 回答
6

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

于 2009-04-29T16:42:46.617 回答
4

在某些会计中,考虑使用整数类型代替或结合使用的可能性。例如,假设您操作的规则要求每个计算结果至少保留 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 会导致错误!仅整数数学消除了该问题。所以我非常规的答案是,如果可能的话,放弃双精度和小数。

于 2016-01-30T23:26:40.140 回答
3

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

于 2009-04-29T16:47:34.920 回答
3

取决于你需要它做什么。

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

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

于 2017-11-07T12:14:08.550 回答
2

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

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

“十进制”的优点。

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

小数的缺点

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

我的观点是,你应该默认使用“十进制”来进行金钱工作和其他精确匹配人工计算很重要的情况,并且在其余时间你应该使用 double 作为默认选择。

于 2016-03-22T16:33:15.683 回答
0

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

于 2009-04-29T16:56:59.187 回答
0

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

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

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

于 2009-04-29T17:16:30.480 回答
0

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);
于 2014-03-17T21:38:30.983 回答
0

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

于 2015-08-17T21:55:09.793 回答