浮点数最大化循环在 D 中不终止,在 C++ 中有效

Floating point maxing out loop doesn't terminate in D, works in 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(在 运行ning 一段时间后)并且 Watch window(我使用 VisualD)显示 (f + e != f) 是 'false' 所以循环应该终止,但在 运行 期间不是这种情况。

我认为汇编可以给出答案,但我不是很擅长。

我是 D 的新手,所以应该是我误用了 language/compiler(使用 DMD 编译,就像 'dmd test.d' 一样,没有其他选项,也来自 VS 和带有默认选项的 VisualD ).任何想法 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 时可能采用双精度到 80 位内部浮点转换。

特别是在扩展尾数时,一些编译器或优化器可以决定让低位 "random" 而不是置零。因此 1.0f,当提供给 FPU 时可能会变成 1.000000000000000000000012134432,根据 float- 精度,仍然是 1.0,但是 1.0000000000000000000000121344321.000000000000000000000089544455(两条尾巴是随机的)通过FPU比较,看起来不一样。

您应该验证 C++ 和 D 编译器如何处理浮点数 extension/reduction 并最终配置适当的开关:如果两个编译器不是来自同一制造商,则它们可能针对各自的默认值做出了不同的选择.

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

但他们有,结果是 f + eps != f 以 80 位扩展精度求值,而
f += 1.0f 使用 32 位浮点数计算。

这意味着循环永远不会结束,因为 f 会在达到
之前停止上升 f + eps != f 达到 false(在 80 位精度下很大)。