什么时候我应该用double代替decimal?

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

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

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

编辑添加: 谢谢你的回复,我从他们身上学到了很多

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

61707 次浏览

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

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

一般来说,如果你的值是瞬态的(没有被重用),使用浮点类型是安全的。浮点类型的真正问题在于以下三种情况。

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

编辑

根据关于c#小数的参考文档:

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

为了澄清我上面的说法:

我倾向于为所有小数进行设计 案件,并依靠一个分析器让 我知道对小数的运算 导致瓶颈或慢速

我只在偏爱小数的行业工作过。如果您正在处理物理或图形引擎,那么为浮点类型(浮点类型或双浮点类型)进行设计可能更有益。

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

  • 十进制= 28-29位有效数字
  • Double = 15-16位有效数字
  • Float = 7位有效数字

编辑2

作为对康拉德•鲁道夫注释的回应,第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的结果也没有那么精确(尽管可能会正确舍入),而且浮点数也远没有那么精确,以至于它被简化为只有两位有效数字。

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

编辑:

关于您关于操作后浮点数精度下降的附加问题,这是一个稍微微妙的问题。实际上,每次操作执行后,精度(我在这里交替使用准确性这个术语)都会稳步下降。这有两个原因:

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

在所有情况下,如果您想比较理论上应该等效的两个浮点数(但使用不同的计算方法得到),则需要允许一定程度的容错(容错程度不同,但通常非常小)。

有关可能引入精度错误的特定情况的更详细概述,请参阅维基百科的文章的Accuracy部分。最后,如果你想在机器级别上对浮点数/操作进行深入(和数学)的讨论,请尝试阅读经常被引用的文章每个计算机科学家都应该知道的浮点运算

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

如果你更看重性能而不是正确性,请使用浮点数。

选择应用程序的功能类型。如果你需要像财务分析那样的精确性,那么你已经回答了你的问题。但如果你的申请可以解决一个估计,你可以接受双倍。

你的申请需要快速计算还是他有足够的时间给你答案?这取决于应用程序的类型。

图形饿了吗?Float或double就足够了。金融数据分析,流星撞击行星的精准度如何?这些都需要一点精确度:)

像其他人建议的那样,以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(市场价值))通常用双数表示更好。

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);

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

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

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

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

在不出现小数的情况下(例如,自动售货机),根本没有理由使用非整型。简单地把它想象成数便士,而不是数美元。我曾见过一些代码,其中每次计算都只涉及整个便士,但使用double会导致错误!只有整数数学解决了这个问题。所以我非传统的回答是,如果可能的话,放弃双位数和小数点。

注意:本文基于http://csharpindepth.com/Articles/General/Decimal.aspx中十进制类型的功能信息以及我自己对其含义的解释。我假设Double是标准的IEEE双精度。

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

“decimal”的优点。

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

十进制的缺点

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

我的观点是,对于金钱工作和其他与人工计算完全匹配很重要的情况,您应该默认使用“decimal”,并且在其他情况下,您应该使用use double作为默认选择。

这取决于你要它做什么。

因为float和double是二进制数据类型,所以在整数舍入的方式上存在<我> < / i >困难和错误,例如double将0.1舍入到0.100000001490116,double也将1 / 3舍入到0.33333334326441。简单地说,不是所有的实数在双类型中都有准确的表示

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