C++ C++;,浮点算术运算如何得到优化?

C++ C++;,浮点算术运算如何得到优化?,c++,optimization,floating-point,C++,Optimization,Floating Point,在x86体系结构上测试极限情况下的简单算术运算时,我观察到一个令人惊讶的行为: const double max = 9.9e307; // Near std::numeric_limits<double>::max() const double init[] = { max, max, max }; const valarray<double> myvalarray(init, 3); const double mysum = myvalarray.sum(); co

在x86体系结构上测试极限情况下的简单算术运算时,我观察到一个令人惊讶的行为:

const double max = 9.9e307; // Near std::numeric_limits<double>::max()
const double init[] = { max, max, max };

const valarray<double> myvalarray(init, 3);
const double mysum = myvalarray.sum();
cout << "Sum is " << mysum << endl;             // Sum is 1.#INF
const double myavg1 = mysum/myvalarray.size();
cout << "Average (1) is " << myavg1 << endl;    // Average (1) is 1.#INF
const double myavg2 = myvalarray.sum()/myvalarray.size();
cout << "Average (2) is " << myavg2 << endl;    // Average (2) is 9.9e+307
(不过,这次gcc将平均值(2)设置为
#INF
:)

  • 有人愿意解释一下这种“效果”是如何实现的吗
  • 这是一个“特征”吗?或者我可以认为这是“意外的行为”而不是简单的“令人惊讶”?

  • 感谢您的猜测,但是:可能是在浮点寄存器中直接计算平均值(2),该寄存器的宽度为80位,溢出时间晚于64位存储器中的双精度存储器。您应该检查代码的反汇编,看看是否确实如此。

    在某些情况下,编译器可以使用比声明的类型所隐含的类型更宽的类型,但好吧,这不是其中之一

    因此,我认为我们有一个类似的效果,在不应该的情况下使用额外的精度

    x86有80位FP内部寄存器。虽然gcc倾向于以最大精度使用它们(因此是bug 323),但我的理解是MSVC将精度设置为53位,即64位的两倍。但延长的显著性并不是80位FP的唯一区别,指数范围也增加了。和IIRC,x86中没有强制使用64位双精度范围的设置

    代码板现在似乎不可访问,否则我会在没有80位长双精度码的体系结构上测试您的代码

    g++ -O0 -g -S test.cpp  -o test.s0
    g++ -O3 -g -S test.cpp  -o test.s3
    
    比较test.s[03]可以看出,实际上valarray::sum甚至不再被调用。我已经看了很长时间了,但以下片段似乎是定义片段:

        .loc 3 16 0 ; test.s0
    
        leal    -72(%ebp), %eax
        movl    %eax, (%esp)
        call    __ZNKSt8valarrayIdE3sumEv
        fstpl   -96(%ebp)
        leal    -72(%ebp), %eax
        movl    %eax, (%esp)
        call    __ZNKSt8valarrayIdE4sizeEv
        movl    $0, %edx
        pushl   %edx
        pushl   %eax
        fildq   (%esp)
        leal    8(%esp), %esp
        fdivrl  -96(%ebp)
        fstpl   -24(%ebp)
    
        .loc 3 17 0
    

        .loc 1 16 0 ; test.s3
        faddl   16(%eax)
        fdivs   LC3
        fstpl   -336(%ebp)
    LVL6:
    LBB449:
    LBB450:
        .loc 4 514 0
    

    这是一种特征,或者至少是故意的。 基本上,x86上的浮点寄存器具有更多 精度和范围比双精度(15位指数,而不是 11,64位matissa,而不是52)。C++标准允许 对中间值使用更高的精度和范围,以及 几乎所有英特尔编译器都会在某些情况下这样做 情况;表现上的差异是显著的。 是否获得扩展精度取决于何时 以及编译器是否溢出到内存中。(将结果保存在 命名变量需要编译器将其转换为实际变量 双精度,至少根据标准) 我见过的更糟糕的情况是一些代码基本上做到了:

    return p1->average() < p2->average()
    
    返回p1->average()average()
    
    ,使用
    average()
    在内部表上执行预期的操作 在数据中。在某些情况下,
    p1
    p2
    实际上是一个关键点 对于相同的元素,但返回值仍然为true; 其中一个函数调用的结果将溢出到 内存(并截断为
    double
    ),另一个 保留在浮点寄存器中

    (该函数用作排序的排序函数,
    sort
    , 结果代码崩溃了,因为由于这种影响 没有定义足够严格的订购标准
    排序
    超出传递给它的范围时的代码。)

    无法访问。
    init
    数组的内容是什么<代码>{9.9e+307,9.9e+307,9.9e+307}?@KennyTM:是的,我忘了我问题中的代码(现在更新)。谢谢。MSVC有一个选项(一致的FP操作)来打开或关闭这个“功能”。事实上,我相信它是允许使用额外精度的地方之一(它在单个语句中,没有显式转换,所有内容都是内联的)。另外,正如引用的错误中所解释的,如果编译器想要充分利用x87 fpu,无论是否应该使用,它都必须使用更高的精度。@Mark B:在阅读引用的错误后,我相信关闭“功能”在这种情况下不会有帮助,因为只有尾数的大小可以减少,而指数的大小不能减少。溢出发生的时间取决于指数。@Mark B:找到了,谢谢你指出。
    return p1->average() < p2->average()