输出以下代码:
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;
}
是
奇怪的是,当我在调试if (count == 323)
之后放置断点并在Visual Studio Watch窗口中放置表达式(a * 0.1) == 0
时,它会报告表达式为true
。
有谁知道为什么表达式a * 0.1
不为零,但是当分配给变量b
时,b
为零?
答案 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位格式的内容。