提问者:小点点

浮点最大化循环不会在D中终止,在C中工作


我有两个类似的程序,一个在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;
}

共2个答案

匿名用户

混合FPU和SSE代码,这是…真的很奇怪。我认为绝对没有理由以这种方式实现它。

但他们有,结果是f EPS!=f以80位扩展精度评估,而
f=1.0f使用32位浮点数评估。

这意味着循环永远不会结束,因为f将在达到使
f EPS!=ffalse(在80位精度下是巨大的)的值之前停止上升。

匿名用户

试图用!=或==使用浮点值来打破循环是在寻找麻烦。

不同的行为不太可能是由于编译器在将值传递给FPU时可能采用的浮点到双精度到80位内部浮点转换。

在扩展尾数时,特别是-一些编译器或优化器可以决定让不太重要的位“随机”而不是归零。所以1.0f,当给FPU时可能会变成1。000000000000000000000012134432认为-根据浮点数-精度,仍然是1.0,但wen1。0000000000000000000000121344321。000000000000000000000089544455(两个尾巴都是随机的)被FPU比较,看起来不同。

您应该验证C和D编译器如何处理浮点扩展/减少,并最终配置适当的开关:如果两个编译器不是来自同一个制造商,他们可能对各自的默认值做出了不同的选择。