我有两个类似的程序,一个在C中,另一个在D中。
编译在Windows7 64位上,到64位二进制文件。
C版本,VS 2013:
#include <iostream>
#include <string>
int main(int argc, char* argv[])
{
float eps = 1.0f;
float f = 0.0f;
while (f + eps != f)
f += 1.0f;
std::cout << "eps = " + std::to_string(eps) + ", max_f = " + std::to_string(f) << std::endl;
return 0;
}
D版本,DMD v2.066.1:
import std.stdio;
import std.conv;
int main(string[] argv)
{
float eps = 1.0f;
float f = 0.0f;
while (f + eps != f)
f += 1.0f;
writeln("eps = " ~ to!string(eps) ~ ", max_f = " ~ to!string(f));
return 0;
}
C版本按预期工作,当f=16777216时发现f e==f。
但是D版本永远存在。当我放置断点时,我看到在D版本f中也有16777216(运行一段时间后)和观察窗口(我使用VisualD)显示(f e!=f)是“false”,因此循环应该终止,但在运行时并非如此。
我认为大会可以给出答案,但我不太擅长。
我是D的新手,所以应该是我滥用了语言/编译器(用DMD编译为“dmd test. d”,没有额外的选项,也用VisualD和默认选项从VS编译)。任何想法D版本的程序可能有什么问题?谢谢!
拆卸:
C:
000000013F7D1410 mov rax,rsp
000000013F7D1413 push rbp
000000013F7D1414 lea rbp,[rax-5Fh]
000000013F7D1418 sub rsp,0E0h
000000013F7D141F mov qword ptr [rbp+17h],0FFFFFFFFFFFFFFFEh
000000013F7D1427 mov qword ptr [rax+8],rbx
000000013F7D142B movaps xmmword ptr [rax-18h],xmm6
000000013F7D142F xorps xmm1,xmm1
float eps = 1.0f;
float f = 0.0f;
000000013F7D1432 movss xmm6,dword ptr [__real@3f800000 (013F7D67E8h)]
000000013F7D143A nop word ptr [rax+rax]
f += 1.0f;
000000013F7D1440 addss xmm1,xmm6
while (f + eps != f)
000000013F7D1444 movaps xmm0,xmm1
000000013F7D1447 addss xmm0,xmm6
000000013F7D144B ucomiss xmm0,xmm1
000000013F7D144E jp main+30h (013F7D1440h)
000000013F7D1450 jne main+30h (013F7D1440h)
D:
000000013F761002 mov ebp,esp
000000013F761004 sub rsp,50h
{
float eps = 1.0f;
000000013F761008 xor eax,eax
000000013F76100A mov dword ptr [rbp-50h],eax
000000013F76100D movss xmm0,dword ptr [rbp-50h]
000000013F761012 movss dword ptr [f],xmm0
float f = 0.0f;
while (f + eps != f)
f += 1.0f;
000000013F761017 movss xmm1,dword ptr [__NULL_IMPORT_DESCRIPTOR+1138h (013F7C3040h)]
000000013F76101F movss xmm2,dword ptr [f]
000000013F761024 addss xmm2,xmm1
000000013F761028 movss dword ptr [f],xmm2
000000013F76102D fld dword ptr [f]
000000013F761030 fadd dword ptr [__NULL_IMPORT_DESCRIPTOR+1138h (013F7C3040h)]
000000013F761036 fld dword ptr [f]
000000013F761039 fucomip st,st(1)
000000013F76103B fstp st(0)
000000013F76103D jne D main+17h (013F761017h)
000000013F76103F jp D main+17h (013F761017h)
接受harold的回答,即程序行为是由于FPU和SSE的混合使用。
以下是D程序集片段中发生的事情的摘要。事实上,循环将永远运行。
当f达到16777216.0时,SSE严格按照IEEE-754行事,我们将该值添加1.0(f=1.0f),我们仍然在xmm2寄存器中获得16777216.0,然后将其存储到内存中。
(f EPS!=f)表达式是在FPU上计算的。由于FPU寄存器具有足够的精度(f EPS),结果为16777217.0。如果我们将此结果存储回内存到浮点变量中,那么我们将获得期望值16777216.0(因为16777217.0未表示为浮点)。并且(f EPS!=f)将为“false”,循环将终止。但我们不会将任何数字存储回内存并在FPU上执行比较(因为我们有两个操作数)。这意味着我们比较一个严格根据IEEE-754(f)计算的数字和另一个以80位精度计算的数字(f EPS)。16777216.0!=16777217.0并且循环永远运行。
我不是这方面的专家,但对我来说,使用SSE指令进行浮点运算似乎更健壮,正如程序的C版本所证明的那样。
我在D论坛上讨论了http://forum.dlang.org/thread/ucnayusylmpvkpcnbhgh@forum.dlang.org
事实证明,程序的行为是正确的——根据语言规范,可以以更高的精度执行中间计算。
任何D编译器的健壮实现是:
import std.stdio;
int main()
{
const float eps = 1.0f;
const float step = 1.0;
float f = 0.0f;
float fPlusEps = f + eps;
while (f != fPlusEps)
{
f += step;
fPlusEps = f + eps;
}
writeln("eps = ", eps, ", max_f = ", f);
return 0;
}
混合FPU和SSE代码,这是…真的很奇怪。我认为绝对没有理由以这种方式实现它。
但他们有,结果是f EPS!=f
以80位扩展精度评估,而f=1.0f
使用32位浮点数评估。
这意味着循环永远不会结束,因为f
将在达到使f EPS!=f
false(在80位精度下是巨大的)的值之前停止上升。
试图用!=或==使用浮点值来打破循环是在寻找麻烦。
不同的行为不太可能是由于编译器在将值传递给FPU时可能采用的浮点到双精度到80位内部浮点转换。
在扩展尾数时,特别是-一些编译器或优化器可以决定让不太重要的位“随机”而不是归零。所以1.0f
,当给FPU时可能会变成1。000000000000000000000012134432
认为-根据浮点数
-精度,仍然是1.0
,但wen1。000000000000000000000012134432
和1。000000000000000000000089544455
(两个尾巴都是随机的)被FPU比较,看起来不同。
您应该验证C和D编译器如何处理浮点扩展/减少,并最终配置适当的开关:如果两个编译器不是来自同一个制造商,他们可能对各自的默认值做出了不同的选择。