输出以下代码:
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)
在调试并(a * 0.1) == 0
在Visual Studio Watch窗口中放置表达式之后放置断点时,它会报告表达式true
.
有谁知道为什么表达式a * 0.1
不为零,但是当分配给变量时b
,则b
为零?
我的特定硬件和CLR版本不会发生这种情况.编辑:哦,是的,它也发生在我身上,如果我使用"x86"(或"任何CPU"启用"首选32位")和"调试"模式.
这样的事情有时会发生的原因是,系统可能会将值保存在80位CPU注册表中,它具有"额外"精度.但是当放入真正的64位时Double
,它会改变价值.
如果你改成:
var isZeroA = (double)(a * 0.1) == 0;
那么正式,你真的没有什么改变(从铸造double
到double
!),但在现实中可能会迫使运行时间从80位转换为64位.它会改变你的输出吗?编辑:这个"无操作"演员为我改变了一些东西!有关C#中具有浮点类型的此类强制转换技巧的更多信息,请参阅其他线程将结果转换为float方法返回float更改结果.
请注意,Double
算术不是确定性的(即,重复时相同的计算可以给出不同的结果),因为这些64位/ 80位问题.请参阅其他线程C#中的浮点数学是否一致?是真的吗?
以下更简单的程序还会显示存在的问题(至少在我的系统上):
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
.
参考:经常引用的文件大卫戈德伯格的每个计算机科学家应该知道的关于浮点运算的内容出现在互联网上,其中增加了一个匿名作者(不是Goldberg)的IEEE 754实现之间的差异.本节" IEEE 754实现之间的差异"解释了您以技术方式看到的问题.
另请参阅有关此80位格式的x86扩展精度格式(维基百科页面部分).