C++ gnuc+中程序的奇怪行为+;,使用浮点数

C++ gnuc+中程序的奇怪行为+;,使用浮点数,c++,gcc,floating-point,double,compiler-optimization,C++,Gcc,Floating Point,Double,Compiler Optimization,看看这个节目: #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) *

看看这个节目:

#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)+dist(C,D)和dist(A,D)+dist(B,C)的值相等,但条件dist(A,B)+dist(C,D)>dist(A,D)+dist(B,C)是真的

当我使用-O2编译时,它看起来还可以:

$ g++ test.cpp -O2 ; ./a.out 
2.8284271247461902909
2.8284271247461902909
我认为,这是一个gcc错误,与浮点运算精度没有直接关系,因为在这种情况下,dist(a,B)+dist(C,D)和dist(a,D)+dist(B,C)的值必须相等(它们都是sqrt(2)+sqrt(2))

当我更改功能区时:

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;
}
与-ffloat存储一起运行:

$ g++ test.cpp -ffloat-store ; ./a.out 
Equal
解决方案: 很可能,这在GCC#323中“不是一个bug”:


使用-ffloat存储进行编译可以解决问题。

这种看似奇怪的行为是由于旧的x87浮点单元的工作方式:它使用80位
长的double
类型,精度为64位作为其寄存器格式,而临时
double
的长度为64位,精度为53位。发生的情况是,编译器将
sqrt(2)
结果中的1个溢出到内存中(因为
sqrt
返回一个
double
,这将舍入该类型的53位有效位),以便FP寄存器堆栈对于下一次调用
sqrt(2)
是清晰的。然后,它将从内存加载的53位精度值与从另一个
sqrt(2)
调用返回的未舍入的64位精度值进行比较,它们的取整方式不同,正如您从这个汇编程序输出中看到的那样(为了清晰起见,使用了第二个代码段,将2s改为2.0s,并将编译标志设置为
-Wall-O0-m32-mfpmath=387-march=i586-fno-builtin
):


结论:x87是个怪胎。
-mfpmath=sse
是这种行为的最终解决方案——它将使GCC将FLT_EVAL_方法定义为0,如sse(2)浮点支持仅为单/双精度。对于此程序,
-ffloat store
开关也可以解决此问题,但不建议将其作为通用解决方案--它会由于额外溢出/填充而使程序变慢,并且不会在所有情况下都工作。当然,使用64位CPU/OS/编译器组合可以解决此问题oo,因为x86-64 ABI默认使用SSE2进行浮点运算。

重复链接的主要答案不太好,但它足以让您了解总体思路并提供链接供进一步阅读……我知道浮点运算的基础知识和IEEE754标准,我理解,为什么0.1+0.2!=0.3。但在这种情况下,值必须是b我们看到,当使用-O0和-O2时,编译器的行为会有所不同。(这不是重复。)CPU内部的计算精度高于
double
。我怀疑在未优化的情况下,比较的其中一侧会以较低的精度临时存储在寄存器中,可能是因为只有一侧会内联,然后以更高的精度进行比较(您需要查看程序集才能确定)如果是这样的话,我会认为这是一个编译器错误。@ Tun-D:是的,也许你是对的,我读了文章()。在哪里描述了同样的问题。谢谢。Denis Kirienko:侧记:考虑使用标准的数学函数<代码>子()>代码>来实现<代码>()。
-mfpmath=sse
是一个明确的解决方案:它使GCC将
FLT_EVAL_方法定义为0并(或多或少)坚持它。
-ffloat store
是一个解决方案,它修复了这个特定的程序,但并非在所有情况下都有效。中提供了未修复的程序示例(在SSE2得到编译器的广泛支持和C99的广泛实施之前写的一份调查显示了当时的情况有多糟糕。现在情况有所改善)同意
-mfpmath=sse
是解决这个问题的最佳方法——我称之为变通方法,因为它完全忽略了x87单元。旁注:将x87控制字中的精度设置降低到53位是不可行的,因为这不会将指数范围限制为与
double
相同的范围,而只是有效位长度。同时设置x87控制字PLUS
-ffloat store
仍然不能保证准确的IEEE 754 binary64结果,因为与IEEE 754规定的单舍入不同,非规范化可以一次双舍入到53位精度,然后是更小的有效精度。add/sub不能做到这一点,但它会导致IEEE754乘法在您只有一个x87时仿真起来相当昂贵,如@Ruslan中所示——我的理解是,前导1在x87扩展精度中是显式的,因此您只能获得63个可用的精度位,而在IEEE双精度中它是隐式的。
$ g++ test.cpp ; ./a.out 
Unequal
$ g++ test.cpp -ffloat-store ; ./a.out 
Equal
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