看看这个节目:
#include <iostream>
#include <cmath>
using namespace std;
typedef pair<int, int> coords;
double dist(coords a, coords b)
{
return sqrt((a.first - b.first) * (a.first - b.first) +
(a.second - b.second) * (a.second - b.second));
}
int main()
{
coords A = make_pair(1, 0);
coords B = make_pair(0, 1);
coords C = make_pair(-1, 0);
coords D = make_pair(0, -1);
cerr.precision(20);
cerr << dist(A, B) + dist(C, D) << endl;
cerr << dist(A, D) + dist(B, C) << endl;
if(dist(A, B) + dist(C, D) > dist(A, D) + dist(B, C))
{
cerr << "*" << endl;
}
return 0;
}
函数dist返回两点之间的距离。 A,B,C,D是正方形的角落。
它应该是dist(A,B)== dist(B,C)== dist(C,D)== dist(D,A)== sqrt(2)。
和dist(A,B)+ dist(C,D)== dist(A,D)+ dist(B,C)== 2 * sqrt(2)
我正在使用GNU / Linux,i586,GCC 4.8.2。
我编译这个程序并运行:
$ g++ test.cpp ; ./a.out
2.8284271247461902909
2.8284271247461902909
*
我们看到,该程序输出dist(A,B)+ dist(C,D)和dist(A,D)+ dist(B,C)的相等值,但条件dist(A,B)+ dist (C,D)&gt; dist(A,D)+ dist(B,C)是真的!
当我使用-O2编译时,它看起来没问题:
$ g++ test.cpp -O2 ; ./a.out
2.8284271247461902909
2.8284271247461902909
我认为,这是一个gcc-bug,它与浮点运算精度没有直接关系,因为在这种情况下值dist(A,B)+ dist(C,D)和dist(A,D) )+ dist(B,C)必须相等(每个都是sqrt(2)+ sqrt(2))。
当我更改功能dist时:
double dist(coords a, coords b)
{
double x = sqrt((a.first - b.first) * (a.first - b.first) + (a.second - b.second) * (a.second - b.second));
return x;
}
程序运行正常。所以问题不在于数字的浮点表示,而是在gcc代码中。
32位编译器的简化示例:
#include <iostream>
#include <cmath>
using namespace std;
int main()
{
if (sqrt(2) != sqrt(2))
{
cout << "Unequal" << endl;
}
else
{
cout << "Equal" << endl;
}
return 0;
}
无优化运行:
$ g++ test.cpp ; ./a.out
Unequal
使用-ffloat-store运行:
$ g++ test.cpp -ffloat-store ; ./a.out
Equal
可能是GCC#323中的“不是错误”:https://gcc.gnu.org/bugzilla/show_bug.cgi?id=323
使用-ffloat-store进行编译可以解决问题。
答案 0 :(得分:4)
这个看似奇怪的行为是由于旧的x87浮点单元的工作方式:它使用80位long double
类型,其64位精度作为其寄存器格式,而临时double
s是64位长,53位精度。会发生什么情况是编译器将sqrt(2)
结果中的一个溢出到内存中(因为sqrt
返回double
,这将转换为该类型的53位有效数字),以便FP寄存器-stack对于sqrt(2)
的下一次调用很清楚。然后,它将从内存加载的53位精度值与从另一个sqrt(2)
调用返回的未经接收的64位精度值进行比较,它们之间的差别不同,因为它们的舍入方式不同,正如你从这个汇编程序输出中看到的那样(注释我的第二个代码片段,2s为清晰度改为2.0s,-Wall -O0 -m32 -mfpmath=387 -march=i586 -fno-builtin
改为Godbolt上的编译标志):
main:
# Prologue
leal 4(%esp), %ecx
andl $-16, %esp
pushl -4(%ecx)
pushl %ebp
movl %esp, %ebp
pushl %ecx
subl $20, %esp
# Argument push (2.0)
subl $8, %esp
movl $0, %eax
movl $1073741824, %edx
pushl %edx
pushl %eax
# sqrt(2.0)
call sqrt
# Return value spill
addl $16, %esp
fstpl -16(%ebp)
# Argument push (2.0)
subl $8, %esp
movl $0, %eax
movl $1073741824, %edx
pushl %edx
pushl %eax
# sqrt(2.0)
call sqrt
addl $16, %esp
# Comparison -- see how one arg is loaded from a spill slot while the other is
# coming from the ST(0) i387 register
fldl -16(%ebp)
fucompp
fnstsw %ax
# Status word interpretation
andb $69, %ah
xorb $64, %ah
setne %al
testb %al, %al
# The branch was here, but it and the code below was snipped for brevity's sake
结论:x87是怪人。 -mfpmath=sse
是此行为的最终解决方案 - 它将使GCC将FLT_EVAL_METHOD
定义为0,因为SSE(2)浮点支持仅为单/双。 -ffloat-store
开关也适用于此程序,但不建议将其作为通用解决方法 - 它会因为额外的溢出/填充而使程序变慢,并且在所有情况下都不起作用。当然,使用64位CPU / OS /编译器组合也可以解决这个问题,因为x86-64 ABI默认使用SSE2进行浮点运算。