Gob*_*ner 14 .net floating-point clr equality ieee-754
我正在为一位同事写一个有启发性的例子,告诉他为什么测试花车的平等往往是个坏主意.我去的例子是添加0.1十次,对1.0(我在我的介绍数值类所示)进行比较.我惊讶地发现两个结果是相同的(代码+输出).
float @float = 0.0f;
for(int @int = 0; @int < 10; @int += 1)
{
@float += 0.1f;
}
Console.WriteLine(@float == 1.0f);
Run Code Online (Sandbox Code Playgroud)
一些调查表明,这个结果不能依赖(很像浮动平等).我发现最令人惊讶的是在其他代码之后添加代码可能会改变计算结果(代码+输出).请注意,此示例具有完全相同的代码和IL,并附加了一行C#.
float @float = 0.0f;
for(int @int = 0; @int < 10; @int += 1)
{
@float += 0.1f;
}
Console.WriteLine(@float == 1.0f);
Console.WriteLine(@float.ToString("G9"));
Run Code Online (Sandbox Code Playgroud)
我知道我不应该在花车上使用平等,因此不应该太在意这一点,但我发现它非常令人惊讶,就像我向大家证明的那样.做的东西后,您进行了计算改变了先前计算的价值?我不认为这是人们通常在脑海中计算的模型.
我不是完全难住了,似乎是安全的假设,有某种优化的改变计算的结果,"平等"的情况下发生的(建筑在调试模式防止"平等"的情况下).显然,当CLR发现稍后需要将浮动框打包时,优化被放弃.
我搜索了一下但找不到这种行为的原因.任何人都能提醒我吗?
Han*_*ant 18
这是JIT优化器工作方式的副作用.如果生成的代码更少,它会做更多的工作.原始代码段中的循环被编译为:
@float += 0.1f;
0000000f fld dword ptr ds:[0025156Ch] ; push(intermediate), st0 = 0.1
00000015 faddp st(1),st ; st0 = st0 + st1
for (int @int = 0; @int < 10; @int += 1) {
00000017 inc eax
00000018 cmp eax,0Ah
0000001b jl 0000000F
Run Code Online (Sandbox Code Playgroud)
当您添加额外的Console.WriteLine()语句时,它会将其编译为:
@float += 0.1f;
00000011 fld dword ptr ds:[00961594h] ; st0 = 0.1
00000017 fadd dword ptr [ebp-8] ; st0 = st0 + @float
0000001a fstp dword ptr [ebp-8] ; @float = st0
for (int @int = 0; @int < 10; @int += 1) {
0000001d inc eax
0000001e cmp eax,0Ah
00000021 jl 00000011
Run Code Online (Sandbox Code Playgroud)
注意地址15与地址17 + 1a的差异,第一个循环将中间结果保留在FPU中.第二个循环将其存储回@float局部变量.当它保留在FPU内部时,结果将以完全精度计算.然而,将它存储回来会将中间结果截断回浮点数,从而在过程中丢失大量精度.
虽然不愉快,但我不相信这是一个错误.x64 JIT编译器的行为方式不同.您可以在connect.microsoft.com上创建案例
仅供参考,C#规范指出这种行为是合法且常见的.有关更多详细信息和类似方案,请参阅以下问题:
归档时间: |
|
查看次数: |
1419 次 |
最近记录: |