我在字典中存储双打时看到了一些奇怪的东西,我对此感到困惑。
这是代码:
Dictionary<string, double> a = new Dictionary<string, double>();
a.Add("a", 1e-3);
if (1.0 < a["a"] * 1e3)
Console.WriteLine("Wrong");
if (1.0 < 1e-3 * 1e3)
Console.WriteLine("Wrong");
第二个if语句按预期工作; 1.0不小于1.0。现在,第一个if语句的计算结果为true。非常奇怪的是,当我将鼠标悬停在if上时,intellisense告诉我错误,但代码很高兴地移动到Console.WriteLine。
这适用于Visual Studio 2008中的C#3.5。
这是浮点精度问题吗?那为什么第二个if语句有效呢?我觉得我在这里缺少一些非常基本的东西。
任何见解都表示赞赏。
Edit2(重新提出问题):
我可以接受数学精度问题,但我现在的问题是:为什么悬停过度评估正确?中间窗口也是如此。我将第一个if语句中的代码粘贴到中间窗口中,并评估为false。
更新
首先,非常感谢所有伟大的答案。
我也在同一台机器上的另一个项目中重新创建这个问题。看看项目设置,我看不出任何差异。看看项目之间的IL,我认为没有区别。看看反汇编,我发现没有明显的差异(除了内存地址)。然而,当我调试原始项目时,我看到:
即时窗口告诉我if是否为false,但代码属于条件。
无论如何,我认为最好的答案是在这些情况下为浮点运算做准备。我不能放弃这个的原因更多的是调试器的计算与运行时的不同。非常感谢Brian Gideon和stephentyrone的一些非常有见地的评论。
这是浮动精度问题。
第二个语句有效,因为编译器在发出.exe之前计算表达式1e-3 * 1e3。
在ILDasm / Reflector中查找它会发出类似的东西
if (1.0 < 1.0)
Console.WriteLine("Wrong");
这里的问题非常微妙。 C#编译器不会(总是)发出以double形式进行计算的代码,即使这是您指定的类型。特别是,它发出的代码使用x87指令以“扩展”精度进行计算,而不会将中间结果舍入为两倍。
根据1e-3是被评估为double还是long double,以及乘法是以double还是long double计算,可以得到以下三个结果中的任何一个:
显然,第一次比较,即未达到预期的比较,正按照我列出的第三种情况中描述的方式进行评估。 1e-3被舍入为双倍,因为你正在存储它并再次加载它,这会强制舍入,或者因为C#将1e-3识别为双精度文字并以这种方式对待它。乘法正在以long double进行评估,因为 C#有一个脑死亡的数字模型 这就是编译器生成代码的方式。
第二次比较中的乘法要么是使用其他两种方法中的一种进行评估,(你可以通过尝试“1> 1e-3 * 1e3”来计算出哪一个),或者编译器在比较之前对乘法的结果进行舍入。在编译时评估表达式时为1.0。
如果没有通过某些构建设置告诉编译器,可能会告诉编译器不要使用扩展精度;启用codegen到SSE2也可以工作。
看到答案here
嗯...奇怪。我无法重现您的问题。我也在使用C#3.5和Visual Studio 2008。我完全按照发布的方式输入了你的例子,我没有看到Console.WriteLine
语句执行。
此外,第二个if语句正在由编译器进行优化。当我在ILDASM / Reflector中检查调试和发布版本时,我没有看到它的证据。这是因为我得到一个编译器警告,说它上面检测到无法访问的代码。
最后,我不知道这可能是一个浮点精度问题。为什么C#编译器会在运行时以静态方式评估两个双精度值?如果真的如此,那么可以说C#编译器有一个bug。
编辑:在更多地考虑之后,我更加确信这不是浮点精度问题。您必须偶然发现编译器或调试器中的错误,或者您发布的代码并不完全代表您正在运行的实际代码。我对编译器中的错误持高度怀疑态度,但调试器中的错误似乎更有可能。尝试重建项目并再次运行它。也许与exe一起编译的调试信息也不同步。