浮点最大输出循环不会在D中终止,适用于C++

Art*_*iuk 9 c++ floating-point d

我有两个类似的程序,一个用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;
}
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

C++版本按预期工作,当f = 16777216时发现f + e == f.

但D版永远挂了.当我放置断点时,我看到D版本f也是16777216(运行一段时间后)和Watch窗口(我使用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)  
Run Code Online (Sandbox Code Playgroud)

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)  
Run Code Online (Sandbox Code Playgroud)

摘要

接受harold的回答,程序行为是由混合的FPU和SSE使用引起的.

以下是D汇编代码段中的内容摘要.事实上,循环将永远运行.

当f达到16777216.0时,SSE严格按照IEEE-754行为,我们在这个值(f + = 1.0f)上加1.0,我们仍然在xmm2寄存器中获得16777216.0,然后我们将它存储到内存中.

(f + eps!= f)表达式在FPU上计算.由于FPU寄存器具有足够的精度(f + eps),因此结果为16777217.0.如果我们将这个结果存储回内存到float变量,那么我们得到期望值16777216.0(因为16777217.0不表示为float).并且(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;
}
Run Code Online (Sandbox Code Playgroud)

har*_*old 6

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

但他们有,并且结果是f + eps != f使用80位扩展精度
f += 1.0f评估,同时使用32位浮点数进行评估.

这意味着循环永远不会结束,因为f在达到
f + eps != f伪造的值(以80位精度,很大)之前将停止上升.

  • 使用GDC或LDC进行编译会产生预期结果.如答案中所解释的,原因是80位精度.DMD的代码方式相当特殊,后端最初来自Digital Mars C,它的设计目的是快速编译代码,而不是最终的最终性能. (4认同)