C#中的Double类型精度不是15位数字吗?

4

我正在测试来自脑筋急转弯:的代码。

        double d1 = 1.000001;

        double d2 = 0.000001;

        Console.WriteLine((d1 - d2) == 1.0);

结果是“False”。当我改变数据类型时:
        decimal d1 = 1.000001M;

        decimal d2 = 0.000001M;

        decimal d3 = d1-d2;

        Console.WriteLine(d3 == 1);

程序输出了正确答案:"True"。
这个问题只使用了浮点数后6位数字。15位数字的精度发生了什么?
6个回答

27

这与精度无关,而是涉及到表示舍入误差。

System.Decimal 能够以显著降低任何舍入错误的风险来表示大型浮点数,而 System.SingleSystem.Double 无法做到这一点,并且会将这些数字四舍五入并创建类似于您示例中看到的问题。

System.Decimal 使用缩放因子来保持小数点的位置,从而允许对给定浮点数进行精确表示,而 System.SingleSystem.Double 只能尽力近似您的值。

有关更多信息,请参见System.Double

请记住,浮点数只能近似表示十进制数, 浮点数的精度决定了其近似表示十进制数的精度。默认情况下,Double 值包含 15 个小数位的精度,但在内部维护最大 17 个数字。 浮点数的精度具有以下几个后果:

  • 对于特定精度而言,两个看起来相等的浮点数可能不相等,因为它们的最低有效位不同。

  • 如果使用十进制数字,则使用浮点数的数学或比较操作可能不会产生相同的结果,因为浮点数可能无法完全近似表示十进制数字。


“Decimal”类型并不能完全消除四舍五入的需要,但它可以最小化由于四舍五入而产生的误差。例如,以下代码的结果为0.9999999999999999999999999999而不是1。 - quant_dev
@quant_dev:好的,我已经编辑了我的回答以反映这一点 :) - Andrew Hare
"System.Decimal使用缩放因子来保存小数点的位置,从而实现给定浮点数的精确表示" -- 这并不正确;例如,1/3即使是使用双精度类型(使用二进制基数)或十进制类型(使用十进制基数),也无法得到精确的表示。实际上,Double和Decimal之间唯一的区别在于基数、指数和有效数字所保留的位数,以及指数是否可以改变符号(Double可以,Decimal不行)。除此之外,它们很相似。System.Decimal并没有任意精度!" - quant_dev
1
我不相信我在任何地方声称System.Decimal具有任意精度。当我说“允许精确表示给定的浮点数”时,我真正意思是指示例中的那个数字。我也会编辑我的答案以反映这一点。 - Andrew Hare
是的,我检查了我的变量值,当它是双精度类型时,d3 的值为0.99999989。 - Broken_Window

8

通常,检查浮点数值是否相等的方法是检查接近相等,即检查差异是否接近该数据类型的最小值(称为epsilon)。例如,

if (Math.Abs(d1 - d2) <= Double.Epsilon) ...

这段代码测试了d1d2是否由相同的比特模式表示,只是最不重要的比特略有不同。

更正 (添加于2015年3月2日)

经过进一步检查,代码应该更像这样:

// Assumes that d1 and d2 are not both zero
if (Math.Abs(d1 - d2) / Math.Max(Math.Abs(d1), Math.Abs(d2)) <= Double.Epsilon) ...

换句话说,取d1d2之间的绝对差值,然后将其按照d1d2中最大的一个进行缩放,最后与Epsilon进行比较。 参考资料
http://msdn.microsoft.com/en-us/library/system.double.epsilon.aspx
http://msdn.microsoft.com/en-us/library/system.double.aspx#Precision

在你提供的链接中,可以看到 Epsilon 是最小的正 Double,并且不等同于机器 Epsilon,后者代表浮点运算中由于舍入而产生的相对误差的上限。那么为什么你的答案与你发布的链接相矛盾呢? - Nicolas

6

decimal 类型实现了十进制浮点数,而 double 类型实现了二进制浮点数。

使用 decimal 类型的优点在于它的四舍五入行为与人类一致,如果你用一个十进制值进行初始化,那么该值将被精确地存储。这仅对有限长度且在可表示范围和精度内的十进制数成立。如果你使用 1.0M/3.0M 进行初始化,则不会像你在纸上写的 0.33333 循环小数那样被精确存储。

如果你使用十进制数来初始化二进制浮点数,则会将其从可读的十进制格式转换为二进制表示形式,这两个值很少是完全相同的。

decimal 类型的主要目的是用于实现金融应用,在 .NET 实现中,它的精度比 double 要高得多,但是二进制浮点数直接受到硬件支持,因此比十进制浮点数操作快得多。

请注意,double 精确到约 15 个有效数字,而不是 15 个小数位。d1 使用的是 7 个有效数字的值,而不是 6 个,而 d2 只有 1 个有效数字。它们的数量级显着不同也没有帮助。


3
浮点数的概念是它们不精确到特定数量的数字。如果您需要这种功能,应该查看 decimal 数据类型。

3

精度并非绝对,因为将十进制和二进制数进行精确转换是不可能的。

在这种情况下,当以二进制表示时,0.1十进制会无限重复。它转化为0.000110011001100110011...并且永远重复。任何精度都无法完全存储它。


2
避免在浮点数上进行相等比较。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接