请注意,
-ffast-math
可能会使编译器忽略/违反 IEEE 规范,请参见
http://gcc.gnu.org/onlinedocs/gcc-4.8.2/gcc/Optimize-Options.html#Optimize-Options :
除了 -Ofast 选项以外,任何 -O 选项都不会打开此选项,因为它可能导致依赖于精确实现 IEEE 或 ISO 数学函数规则/规范的程序输出不正确。但是,对于不需要这些规范保证的程序,它可能会产生更快的代码。
因此,使用
-ffast-math
不能保证您在应该看到无穷大时看到无穷大。
特别地,
-ffast-math
打开了
-ffinite-math-only
,参见
http://gcc.gnu.org/wiki/FloatingPointMath,意味着(来自
http://gcc.gnu.org/onlinedocs/gcc-4.8.2/gcc/Optimize-Options.html#Optimize-Options):
[...] 浮点算术的优化假定参数和结果不是 NaN 或 +-Inf
这意味着,通过启用
-ffast-math
,您向编译器承诺您的代码永远不会使用无穷大或 NaN,从而允许编译器通过将对
isinf
或
isnan
的任何调用替换为常量
false
(并进一步进行优化)来优化代码。如果您违反了对编译器的承诺,则编译器不需要创建正确的程序。
因此,答案很简单,如果您的代码可能具有无限大或NaN(这在您使用
isinf
和
isnan
的事实强烈暗示),则不能启用
-ffast-math
,否则您可能会得到不正确的代码。
您对
my_isnan
的实现(在某些系统上)有效,因为它直接检查浮点数的二进制表示。当然,处理器仍然可能进行(一些)实际计算(取决于编译器执行哪些优化),因此实际NaN可能出现在内存中,您可以检查其二进制表示,但如上所述,
std::isnan
可能已被替换为常量
false
。同样可能发生的是,编译器将
sqrt
等替换为某些版本,该版本甚至不会为输入
-1
产生NaN。为了查看编译器执行的优化,请将代码编译为汇编语言并查看该代码。
我来做一个(不完全相关的)比喻,如果你告诉编译器你的代码是用C++写的,那么你就不能期望它能正确编译C代码,反之亦然(实际上有一些例子,例如
Can code that is valid in both C and C++ produce different behavior when compiled in each language?)。
启用
-ffast-math
并使用
my_isnan
是一个不好的主意,因为这会使一切都非常依赖于机器和编译器,你不知道编译器总体进行了什么优化,所以可能会有其他隐藏的问题与你使用非有限数学相关,但告诉编译器相反有关。
一个简单的解决方法是使用
-ffast-math -fno-finite-math-only
,这样仍然会给出一些优化。
也许你的代码看起来像这样:
- 过滤掉所有的无穷大和NaN
- 对筛选出来的值进行一些有限的数学运算(我的意思是保证不会产生无穷大或NaN的数学运算,这必须非常小心地检查)
在这种情况下,您可以分割您的代码,并使用优化的 #pragma
或 __attribute__
来有选择地打开和关闭 -ffast-math
(分别为 -ffinite-math-only
和 -fno-finite-math-only
),用于给定的代码片段(但我记得某个版本的GCC与此相关存在一些问题),或者只需将您的代码拆分成单独的文件并使用不同的标志编译它们。当然,如果您能够隔离可能出现无穷大和NaN的部分,则这也适用于更一般的设置。如果您不能隔离这些部分,则这是您不能在此代码中使用 -ffinite-math-only
的强烈指示。
最后,重要的是要理解-ffast-math
不是一种无害的优化,仅仅使程序更快而已。它不仅影响代码的性能,还会影响其正确性(除了浮点数已经存在的所有问题之外,如果我没记错William Kahan在他的主页上有一系列恐怖故事,请参阅每个程序员都应该知道的浮点运算知识)。简而言之,您可能会得到更快的代码,但也可能得到错误或意外的结果(请参见下面的示例)。因此,只有在确实知道自己在做什么并且已经确定要么:
- 这些优化不会影响特定代码的正确性,或者
- 由优化引入的错误对代码不是关键的时候,才应使用这些优化。
使用这种优化与否实际上会导致程序代码表现出不同的行为。特别是当启用像-ffast-math
这样的优化时,它可能会表现出错误(或至少与您的期望完全相反)。例如,考虑以下程序:
#include <iostream>
#include <limits>
int main() {
double d = 1.0;
double max = std::numeric_limits<double>::max();
d /= max;
d *= max;
std::cout << d << std::endl;
return 0;
}
不使用任何优化标志编译时,将输出预期的1
;但使用-ffast-math
标志编译时,将输出0
。
clang
5.1,您的示例代码将打印1
。 - Sylvain Defresnestd::log(0.0)
会有帮助,以查看是std::log
还是std::isinf
计算了错误的结果。 - microtherionmy_isnan(45.0) == 1
。 - Jean-Michaël Celerier(u.x << 1) > (0x7ff0000000000000u << 1)
。 - Albert