浮点数最大化循环在 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.000000000000000000000012134432
和 1.000000000000000000000089544455
(两条尾巴是随机的)通过FPU比较,看起来不一样。
您应该验证 C++ 和 D 编译器如何处理浮点数 extension/reduction 并最终配置适当的开关:如果两个编译器不是来自同一制造商,则它们可能针对各自的默认值做出了不同的选择.
混合的 FPU 和 SSE 代码,那..真的很奇怪。我认为完全没有理由以这种方式实施它。
但他们有,结果是 f + eps != f
以 80 位扩展精度求值,而
f += 1.0f
使用 32 位浮点数计算。
这意味着循环永远不会结束,因为 f
会在达到
之前停止上升
f + eps != f
达到 false(在 80 位精度下很大)。
我有两个类似的程序,一个用 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.000000000000000000000012134432
和 1.000000000000000000000089544455
(两条尾巴是随机的)通过FPU比较,看起来不一样。
您应该验证 C++ 和 D 编译器如何处理浮点数 extension/reduction 并最终配置适当的开关:如果两个编译器不是来自同一制造商,则它们可能针对各自的默认值做出了不同的选择.
混合的 FPU 和 SSE 代码,那..真的很奇怪。我认为完全没有理由以这种方式实施它。
但他们有,结果是 f + eps != f
以 80 位扩展精度求值,而
f += 1.0f
使用 32 位浮点数计算。
这意味着循环永远不会结束,因为 f
会在达到
之前停止上升
f + eps != f
达到 false(在 80 位精度下很大)。