使用/fp:fast出现奇怪的结果

4
我们有一些像这样的代码:
inline int calc_something(double x) {
  if (x > 0.0) {
    // do something
    return 1;
  } else {
    // do something else
    return 0;
  }
}

很不幸,在使用标志/fp:fast时,我们得到了calc_something(0)==1,因此我们显然走了错误的代码路径。只有在我们在代码中多个位置使用不同参数使用该方法时,才会出现这种情况,因此我认为编译器(Microsoft Visual Studio 2008,SP1)进行了一些可疑的优化。

另外,当我们将接口更改为以下内容时,上述问题就会消失:

inline int calc_something(const double& x) {

但我不知道为什么这会修复奇怪的行为。有人能解释一下这种行为吗?如果我无法理解发生了什么,我们将不得不移除/fp:fast开关,但这将使我们的应用程序变慢相当多。


2
你能发布一些对 calc_something 的调用吗? - fredoverflow
您需要绝对的准确性来进行此测试(即0.0真正意义上是0.0)。如果启用fp:fast,通常不要期望像这样的任何东西 :o) - MaR
@fred,你是什么意思?我只是在调用例如 int x = calc_something(0.0); - martinus
对于任何寻求快速答案的人:(1)关于fp:fast的权威MSDN文章。(2)通常情况下,不要在任何第三方源代码上启用fp:fast。如果它是安全的,那么启用它应该由他们来完成,而不是你。 (3)通常情况下,在整个项目中启用它是没有意义的;正确的用法是在程序员可以验证算法准确性、生成的机器指令和性能增益影响的小代码段中选择性地启用它。 - rwong
5个回答

6
我对FPUs不够熟悉,无法确定,但我猜测编译器会让一个现有的值坐在比较中,这个值应该等于x。也许你执行y = x + 20.; y = y - 20;时,y已经在FP堆栈上了,因此编译器不加载x,而是与y进行比较。但由于舍入误差,y并不像它应该是的0.0,所以你会得到奇怪的结果。
更好的解释请参见C++FAQ lite中的为什么cos(x) != cos(y),即使x == y?。这就是我试图传达的内容的一部分,只是我一直记不清楚我在哪里读到过,直到现在为止。
将其更改为const引用可以解决这个问题,因为编译器担心别名问题。它强制从x加载,因为它不能假设其值在创建y之后没有发生变化,而由于x实际上恰好是0.0 [在我熟悉的每种浮点格式中都可以表示],舍入误差消失了。
我相信MS提供了一个编译指示,允许您在每个函数的基础上设置FP标志。或者您可以将此例程移动到单独的文件中,并为该文件提供自定义标志。无论哪种方式,它都可以防止整个程序受到影响,只是为了使那个例程满意。

2
是的,在Visual Studio中尝试使用#pragma float_control(...):http://msdn.microsoft.com/en-us/library/45ec64h6.aspx - 你可以在相关函数周围禁用fp:fast。 - AshleysBrain
干得好!感谢您提供了出色的答案并分享了精彩的文章,Dennis。问题是“为什么cos(x)!= cos(y),即使x == y?”简短的回答是“因为计算机在其核心上不具备上下文感知能力”。 - vulcan raven

2
“calc_something(0L)”和“calc_something(0.0f)”的结果是什么?这可能与强制类型转换之前类型的大小有关。整数占用4个字节,双精度浮点数占用8个字节。

您尝试查看汇编代码以了解上述转换是如何进行的吗?

通过谷歌搜索“fp fast”,我找到了this post [social.msdn.microsoft.com]


对于 calc(0L), calc(0), calc(0.0), calc(0.0f) 的计算结果都是相同的错误结果。 - martinus

2
正如我在其他问题中所说,编译器在生成浮点代码方面表现不佳。Dennis链接的文章很好地解释了这些问题。这里有另一篇文章:MSDN文章
如果代码的性能很重要,您可以通过编写自己的汇编代码轻松地超越编译器。如果您的算法是可向量化的,则还可以使用SIMD(尽管会略微损失精度)。
注1:假设您理解FPU的工作方式。

你的 MSDN 文章在某个地方出了问题。 - Dennis Zickefoose

2

inline int calc_something(double x)(可能)会使用一个80位的寄存器。inline int calc_something(const double& x)会将double存储在内存中,需要64位。至少这解释了两者之间的区别。

然而,我发现你的测试有点可疑。 calc_something 的结果对其输入的舍入非常敏感。您的浮点算法应该对舍入具有鲁棒性。calc_something(1.0-(1.0/3.0)*3)应该与calc_something(0.0)相同。


1

我认为这种行为是正确的。

你永远不会将浮点数比较到小于其持有类型精度的程度。

来自零的某些东西可能等于、大于或小于另一个零。

请参见http://floating-point-gui.de/


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接