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

时间:2023-02-10 22:58:51

Output of the following code:

输出以下代码:

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

is


IsZeroA: False, IsZeroB: True

Strangely, when I put a breakpoint after if (count == 323) while debugging and put expression (a * 0.1) == 0 in Visual Studio Watch window, it reports that expression is true.

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

Does anyone know why expression a * 0.1 is not zero, but when assigned to a variable b, then b is zero?

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

1 个解决方案

#1


9  

This does not happen with my particular hardware and CLR version. Edit: Oh yes, it happen to me too, if I use "x86" (or "Any CPU" with "Prefer 32-bit" enabled) and "Debug" mode.

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

The reason why things like that may sometimes happen, is that the system may hold the value in a 80-bit CPU registry where it has "extra" precision. But when put into a real 64-bit Double, it changes value.

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

If you change into:

如果你改成:

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

then formally you really change nothing (a cast from double to double!), but in reality that may force the run-time to convert from 80-bit to 64-bit. Does it change the output for you? Edit: This "no-op" cast changes something for me! For more on such cast-to-self tricks with floating-point types in C#, see other thread Casting a result to float in method returning float changes result.

然后正式你真的没有改变任何东西(从双倍到双倍的转换!),但实际上可能会迫使运行时从80位转换为64位。它会改变你的输出吗?编辑:这个“无操作”演员改变了我的想法!有关C#中具有浮点类型的此类强制转换技巧的更多信息,请参阅其他线程将结果转换为浮动方法返回浮点更改结果。

Note that Double arithmetic is not deterministic (i.e. the same calculation can give different results when repeated) because of these 64-bit/80-bit issues. See other thread Is floating-point math consistent in C#? Can it be?

注意,由于这些64位/ 80位问题,Double算法不是确定性的(即,重复时相同的计算可以给出不同的结果)。请参阅其他线程C#中的浮点数学是否一致?是真的吗?


The following simpler program also shows the issue in cases where it is present (at least on my system):

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

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!

You can even start with j = 1E-323 in that code. It leads to the same Double.

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


Reference: The often cited document 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 by an anonymous author (which is not Goldberg). This section, Differences Among IEEE 754 Implementations, explains the issue you see in a technical manner.

参考:经常引用的文件大卫戈德伯格的每个计算机科学家应该知道的关于浮点运算的内容出现在互联网上,其中增加了一个匿名作者(不是Goldberg)的IEEE 754实现之间的差异。本节“IEEE 754实现之间的差异”解​​释了您以技术方式看到的问题。

Also see x86 Extended Precision Format (Wikipedia page section) about this 80-bit format.

另请参阅有关此80位格式的x86扩展精度格式(*页面部分)。

#1


9  

This does not happen with my particular hardware and CLR version. Edit: Oh yes, it happen to me too, if I use "x86" (or "Any CPU" with "Prefer 32-bit" enabled) and "Debug" mode.

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

The reason why things like that may sometimes happen, is that the system may hold the value in a 80-bit CPU registry where it has "extra" precision. But when put into a real 64-bit Double, it changes value.

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

If you change into:

如果你改成:

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

then formally you really change nothing (a cast from double to double!), but in reality that may force the run-time to convert from 80-bit to 64-bit. Does it change the output for you? Edit: This "no-op" cast changes something for me! For more on such cast-to-self tricks with floating-point types in C#, see other thread Casting a result to float in method returning float changes result.

然后正式你真的没有改变任何东西(从双倍到双倍的转换!),但实际上可能会迫使运行时从80位转换为64位。它会改变你的输出吗?编辑:这个“无操作”演员改变了我的想法!有关C#中具有浮点类型的此类强制转换技巧的更多信息,请参阅其他线程将结果转换为浮动方法返回浮点更改结果。

Note that Double arithmetic is not deterministic (i.e. the same calculation can give different results when repeated) because of these 64-bit/80-bit issues. See other thread Is floating-point math consistent in C#? Can it be?

注意,由于这些64位/ 80位问题,Double算法不是确定性的(即,重复时相同的计算可以给出不同的结果)。请参阅其他线程C#中的浮点数学是否一致?是真的吗?


The following simpler program also shows the issue in cases where it is present (at least on my system):

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

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!

You can even start with j = 1E-323 in that code. It leads to the same Double.

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


Reference: The often cited document 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 by an anonymous author (which is not Goldberg). This section, Differences Among IEEE 754 Implementations, explains the issue you see in a technical manner.

参考:经常引用的文件大卫戈德伯格的每个计算机科学家应该知道的关于浮点运算的内容出现在互联网上,其中增加了一个匿名作者(不是Goldberg)的IEEE 754实现之间的差异。本节“IEEE 754实现之间的差异”解​​释了您以技术方式看到的问题。

Also see x86 Extended Precision Format (Wikipedia page section) about this 80-bit format.

另请参阅有关此80位格式的x86扩展精度格式(*页面部分)。