不同的浮点结果启用优化 – 编译器错误?

下面的代码可以在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; } 

输出应该是:

 4.5 4.6 

但是g ++优化( O1O3 )会输出:

 4.5 4.5 

如果我在t之前添加volatile关键字,它会起作用,所以可能会有某种优化错误?

在g ++ 4.1.2和4.4.4上testing。

这是ideone的结果: http ://ideone.com/Rz937

而我在g ++上testing的选项很简单:

 g++ -O2 round.cpp 

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

进一步问题:

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

因为我testing过的g ++版本随CentOS / Red Hat Linux 5和CentOS / Redhat 6一起提供

我在这些平台下编译了我的许多程序,我担心这会在我的程序中引起意外的错误。 调查我所有的C ++代码和使用库是否有这样的问题似乎有点困难。 任何build议?

是否有人为什么甚至/fp:fast开启,Visual Studio 2008仍然有效? 这个问题似乎像Visual Studio 2008比g ++更可靠?

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

使用-ffloat-store gcc选项可获得具有不同优化级别的相同浮点结果。

或者,使用long doubletypes(gcc上通常为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. 

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

正如Maxim Yegorushkin在他的回答中已经指出的那样,问题的一部分是在您的计算机内部使用80位浮点表示法。 但这只是问题的一部分。 这个问题的基础是任何forms的n.nn5都没有确切的二进制浮点表示。 那些angular落案件总是不准确的数字。

如果你真的希望你的四舍五入能够可靠地把这些angular落案件,圆形algorithm,解决n.n5,n.nn5,或n.nnn5等(但不是n.5)总是事实不精确。 找出决定某个input值向上或向下取整的angular落案例,并根据与该angular落案例的比较返回向上或向下取整的值。 而且你需要注意的是,优化编译器不会把这个find的angular落案例放在扩展的精确寄存器中。

请参阅Excel如何成功轮值浮动数字,即使它们不精确? 对于这样的algorithm。

或者,你也可以忍受这样一个事实,即angular落案件有时会错误地发生。

不同的编译器有不同的优化设置。 根据IEEE 754,其中一些更快的优化设置不会保持严格的浮点规则。 Visual Studio有一个特定的设置, /fp:strict/fp:precise/fp:fast ,其中/fp:fast违反了可以完成的标准。 你可能会发现这个标志是控制这种设置的优化。 你也可以在GCC中find一个类似的设置来改变行为。

如果是这样的话,那么编译器之间唯一不同的是GCC会在更高的优化级别上默认寻找最快的浮点行为,而Visual Studio不会改变具有更高优化级别的浮点行为。 因此,它可能不一定是一个实际的错误,而是一个你不知道你正在打开的选项的预期行为。

对于那些不能重现该错误的人:不要取消注释掉的debugging信号,它们会影响结果。

这意味着问题与debugging语句有关。 它看起来像是在输出语句中将值加载到寄存器引起的舍入错误,这就是为什么其他人发现你可以用-ffloat-store

进一步问题:

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

为了轻浮,一定有一些程序员不打开-ffloat-store ,否则这个选项就不存在了(同样,一定程序员确实打开了-ffloat-store )。 我不build议总是打开或closures它。 打开它可以防止一些优化,但closures它可以让你获得的行为。

但是,通常情况下,二进制浮点数(如计算机使用)与十进制浮点数(人们熟悉的)之间存在一些不匹配 ,而这种不匹配会导致类似的行为,你得到的不是由这种不匹配造成的,但类似的行为可以 )。 问题是,既然你在处理浮点的时候已经有些模糊了,我不能说这个-ffloat-store使它更好或者更糟。

相反,你可能想研究一下你想解决的问题的其他解决scheme (不幸的是,柯尼格并没有指出实际的论文,我也找不到一个明显的“规范”的地方,所以我必须将您发送给Google )。


如果你不是四舍五入的输出目的,我可能会看std::modf() (在cmath )和std::numeric_limits<double>::epsilon()limits )。 考虑到原来的round()函数,我相信通过调用这个函数来replace对std::floor(d + .5)的调用会更简洁:

 // this still has the same problems as the original rounding function int round_up(double d) { // return value will be coerced to int, and truncated as expected // you can then assign the int to a double, if desired return d + 0.5; } 

我认为这提示了以下改进:

 // this won't work for negative d ... // this may still round some numbers up when they should be rounded down int round_up(double d) { double floor; d = std::modf(d, &floor); return floor + (d + .5 + std::numeric_limits<double>::epsilon()); } 

简单的说明: std::numeric_limits<T>::epsilon()被定义为“添加到1的最小数字,创build一个不等于1的数字”。 你通常需要使用一个相对的epsilon(即,以某种方式来衡量你使用的数字不是“1”)。 d.5std::numeric_limits<double>::epsilon()的总和应该接近1,所以对std::numeric_limits<double>::epsilon()分组意味着std::numeric_limits<double>::epsilon()的大小约为我们在做什么 如果有的话, std::numeric_limits<double>::epsilon()将会太大(当三者的和小于1时),并且可能会导致我们在不应该的时候将一些数字加起来。


现在,你应该考虑std::nearbyint()

就我个人而言,我遇到了相同的问题 – 从gcc到VS. 在大多数情况下,我认为最好避免优化。 唯一值得的是当你处理涉及浮点数据大数组的数值方法时。 即使在拆解之后,我经常不知道编译器的select。 通常使用编译器内部函数更容易,或者只是自己编写程序集。