为什么在变量中存储值会改变相等比较的结果?

时间:2015-12-04 10:39:33

标签: c# .net clr

输出以下代码:

var a = 0.1;
var count = 1;

while (a > 0)
{
    if (count == 323)
    {
        var isZeroA = (a * 0.1) == 0;
        var b = a * 0.1;
        var isZeroB = b == 0;

        Console.WriteLine("IsZeroA: {0}, IsZeroB: {1}", isZeroA, isZeroB);
    }

    a *= 0.1;
    ++count;
}

IsZeroA:False,IsZeroB:True

奇怪的是,当我在调试if (count == 323)之后放置断点并在Visual Studio Watch窗口中放置表达式(a * 0.1) == 0时,它会报告表达式为true

有谁知道为什么表达式a * 0.1不为零,但是当分配给变量b时,b为零?

1 个答案:

答案 0 :(得分:9)

我的特定硬件和CLR版本不会发生这种情况。 编辑:哦,是的,它也发生在我身上,如果我使用“x86”(或“任何CPU”启用了“Prefer 32-bit”)和 “调试”模式。

有时会发生这种情况的原因是,系统可能会将值保存在80位CPU注册表中,并且具有“额外”精度。但是当放入真正的64位Double时,它会改变值。

如果您改为:

var isZeroA = (double)(a * 0.1) == 0;

然后正式地你真的没有改变任何东西(从double转换为double!),但实际上可能迫使运行时从80位转换为64位。它会改变你的输出吗? 编辑:这个“无操作”演员为我改变了一些东西!有关C#中具有浮点类型的此类强制转换技巧的更多信息,请参阅其他线程Casting a result to float in method returning float changes result

请注意,Double算术 确定性(即相同的计算在重复时会产生不同的结果)因为这些64位/ 80位的问题。请参阅其他帖子Is floating-point math consistent in C#? Can it be?

以下更简单的程序还会显示存在的问题(至少在我的系统上):

double j = 9.88131291682493E-324;
Console.WriteLine(j * 0.1 == 0);             // "False"
double k = j * 0.1;
Console.WriteLine(k == 0);                   // "True"

Console.WriteLine((double)(j * 0.1) == 0);   // "True", double-to-double cast!

您甚至可以从该代码中的j = 1E-323开始。它导致相同的Double

参考:David Goldberg经常引用的文件What Every Computer Scientist Should Know About Floating-Point Arithmetic出现在互联网上,an added section Differences Among IEEE 754 Implementations由匿名作者(不是Goldberg)出现。本节 IEEE 754实施中的差异 ,解释了您以技术方式看到的问题。

另请参阅x86 Extended Precision Format (Wikipedia page section)有关此80位格式的内容。