启用优化后会产生不同的浮点结果 - 编译器错误?

Bea*_*ear 103 c++ optimization g++ c++-faq

以下代码适用于Visual Studio 2008,有无优化.但它只适用于没有优化的G ++(O0).

#include <cstdlib>
#include <iostream>
#include <cmath>

double round(double v, double digit)
{
    double pow = std::pow(10.0, digit);
    double t = v * pow;
    //std::cout << "t:" << t << std::endl;
    double r = std::floor(t + 0.5);
    //std::cout << "r:" << r << std::endl;
    return r / pow;
}

int main(int argc, char *argv[])
{
    std::cout << round(4.45, 1) << std::endl;
    std::cout << round(4.55, 1) << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

输出应该是:

4.5
4.6
Run Code Online (Sandbox Code Playgroud)

但是带有优化(O1- O3)的g ++ 将输出:

4.5
4.5
Run Code Online (Sandbox Code Playgroud)

如果我volatile在t之前添加关键字,它可以工作,那么可能存在某种优化错误吗?

测试g ++ 4.1.2和4.4.4.

以下是ideone的结果:http://ideone.com/Rz937

我在g ++上测试的选项很简单:

g++ -O2 round.cpp
Run Code Online (Sandbox Code Playgroud)

更有趣的结果,即使我/fp:fast在Visual Studio 2008上打开选项,结果仍然是正确的.

进一步问题:

我想知道,我应该总是打开-ffloat-store选项吗?

因为我测试的g ++版本附带了CentOS/Red Hat Linux 5和CentOS/Redhat 6.

我在这些平台下编译了很多程序,我担心它会在我的程序中引起意外的错误.调查我的所有C++代码并使用库是否有这样的问题似乎有点困难.有什么建议吗?

是否有人对甚至/fp:fast打开的原因感兴趣,Visual Studio 2008仍然有效?看起来Visual Studio 2008在这个问题上比g ++更可靠吗?

Max*_*kin 87

Intel x86处理器内部使用80位扩展精度,而double通常为64位宽.不同的优化级别会影响CPU的浮点值保存到内存中的频率,从而将其从80位精度舍入到64位精度.

使用-ffloat-storegcc选项可以获得具有不同优化级别的相同浮点结果.

或者,使用long doublegcc上通常为80位宽的类型,以避免从80位到64位精度的舍入.

man gcc 说的一切:

   -ffloat-store
       Do not store floating point variables in registers, and inhibit
       other options that might change whether a floating point value is
       taken from a register or memory.

       This option prevents undesirable excess precision on machines such
       as the 68000 where the floating registers (of the 68881) keep more
       precision than a "double" is supposed to have.  Similarly for the
       x86 architecture.  For most programs, the excess precision does
       only good, but a few programs rely on the precise definition of
       IEEE floating point.  Use -ffloat-store for such programs, after
       modifying them to store all pertinent intermediate computations
       into variables.
Run Code Online (Sandbox Code Playgroud)

  • 我认为这就是答案.常量4.55转换为4.54999999999999,这是64位中最接近的二进制表示; 乘以10并再次舍入到64位,你得到45.5.如果通过将其保存在80位寄存器中而跳过舍入步骤,则最终得到45.4999999999999. (20认同)
  • @Bear,debug语句可能导致该值从寄存器刷新到内存中. (5认同)
  • @Bear,通常你的应用程序应该受益于扩展的精度,除非它在64位浮点数低于或溢出并产生"inf"时操作极小或极大的值.没有好的经验法则,单元测试可以给你一个明确的答案. (2认同)
  • @bear作为一般规则,如果你需要完全可预测的结果和/或人类在纸上做的总和,那么你应该避免浮点数.-ffloat-store消除了一个不可预测的来源,但它不是一个神奇的子弹. (2认同)

Dav*_*men 10

输出应该是:4.5 4.6如果您具有无限精度,或者您正在使用使用基于十进制而不是基于二进制的浮点表示的设备,那么输出将是什么.但是,你不是.大多数计算机使用二进制IEEE浮点标准.

正如Maxim Yegorushkin在他的回答中已经提到的,问题的一部分是你的计算机内部正在使用80位浮点表示.不过,这只是问题的一部分.该问题的基础是任何数量的n.nn5形式都没有精确的二进制浮点表示.那些极端情况总是不精确的数字.

如果你真的希望你的舍入能够可靠地围绕这些极端情况,你需要一个舍入算法来解决n.n5,n.nn5或n.nnn5等(但不是n.5)总是这样的事实不精确.找到确定某个输入值是向上​​还是向下舍入的角点大小写,并根据与此角点大小写的比较返回向上舍入或向下舍入的值.而且你需要注意优化编译器不会将找到的极移情况放在扩展精度寄存器中.

请参阅Excel如何成功舍入浮动数字,即使它们不精确?对于这样的算法.

或者你可以忍住这样一个事实,即角落的情况有时会错误地绕过.


Pup*_*ppy 6

不同的编译器具有不同的优化设置.根据IEEE 754,其中一些更快的优化设置不保持严格的浮点规则.Visual Studio中有一个特定的设置,/fp:strict,/fp:precise,/fp:fast,其中/fp:fast违反了什么可以做标准.您可能会发现标志控制此类设置中的优化.您可能还会在GCC中找到类似的设置来更改行为.

如果是这种情况,那么编译器之间唯一不同的是GCC会在更高优化时默认寻找最快的浮点行为,而Visual Studio不会更高优化级别的浮点行为.因此,它可能不一定是一个真正的错误,但你不知道你正在打开的选项的预期行为.

  • 有一个用于GCC的`-ffast-math`开关,并且它没有被引用的任何`-O`优化级别打开:"它可能导致程序的输出不正确,这取决于IEEE或ISO的精确实现数学函数的规则/规范." (4认同)