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

Why storing a value in a variable changes the outcome of equality comparison?

以下代码的输出:

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:假,IsZeroB:真

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

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

我的特定硬件和 CLR 版本不会发生这种情况。 编辑:哦,是的,如果我使用 "x86"(或 "Any CPU" 和 "Prefer 32-bit" 启用)和 "Debug" 模式。

之所以有时会发生这种情况,是因为系统可能会将值保存在具有 "extra" 精度的 80 位 CPU 注册表中。但是当放入真正的 64 位 Double 时,它会改变值。

如果你变成:

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

然后正式你真的什么都没有改变(从 doubledouble 的转换!),但实际上这可能会迫使 运行-时间从 80 位转换到64 位。它会改变你的输出吗? 编辑: 这个 "no-op" 演员对我来说改变了一些东西!有关 C# 中浮点类型的此类转换为自我的技巧的更多信息,请参阅其他线程 Casting a result to float in method returning float changes result.

请注意,Double 算术 不是 确定性的(即相同的计算在重复时可以给出不同的结果)因为这些 64-bit/80-bit 问题。查看其他线程 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.


参考文献:经常被引用的文档 What Every Computer Scientist Should Know About Floating-Point Arithmetic by David Goldberg appears in the internet with an added section Differences Among IEEE 754 Implementations 由一位匿名作者(不是 Goldberg)撰写。本节 IEEE 754 实现之间的差异 以技术方式解释了您遇到的问题。

另请参阅 x86 Extended Precision Format (Wikipedia page section) 关于此 80 位格式的信息。