请注意,这-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#优化选项)
[...] 浮点算术优化,假设参数和结果不是 NaN 或 +-Infs
这意味着,通过使您向编译器做出承诺,您的代码将永远不会使用无穷大或 NaN,这反过来又允许编译器通过例如替换对常量或常量的-ffast-math
任何调用来优化代码(并进一步优化从那里)。如果你违背了对编译器的承诺,编译器不需要创建正确的程序。isinf
isnan
false
isinf
因此,答案很简单,如果您的代码可能有无穷大或 NaN(您使用and的事实强烈暗示了这一点isnan
),您不能启用-ffast-math
,否则您可能会得到不正确的代码。
您的实现my_isnan
工作(在某些系统上),因为它直接检查浮点数的二进制表示。当然,处理器仍然可能进行(一些)实际计算(取决于编译器所做的优化),因此实际的 NaN 可能会出现在内存中,您可以检查它们的二进制表示,但如上所述,std::isnan
可能已被替换为常数false
。同样也可能发生编译器替换,例如,sqrt
用一些甚至不为 input 生成 NaN 的版本-1
。为了查看您的编译器做了哪些优化,编译到汇编器并查看该代码。
做一个(不是完全不相关的)类比,如果你告诉你的编译器你的代码是用 C++ 编写的,你不能指望它正确编译 C 代码,反之亦然(有这方面的实际例子,例如Can code that is valid在 C 和 C++ 中,在每种语言中编译时都会产生不同的行为?)。
-ffast-math
启用和使用是一个坏主意,my_isnan
因为这会使一切都非常依赖于机器和编译器,您不知道编译器总体上做了哪些优化,因此可能存在与您使用非有限数学,但否则告诉编译器。
一个简单的解决方法是使用-ffast-math -fno-finite-math-only
它仍然会提供一些优化。
也可能是您的代码看起来像这样:
- 过滤掉所有无穷大和 NaN
- 对过滤后的值进行一些有限的数学运算(我的意思是保证永远不会产生无穷大或 NaN 的数学运算,必须非常非常仔细地检查)
在这种情况下,您可以拆分代码并使用 optimize#pragma
或(分别和)为给定的代码段选择性地__attribute__
打开和关闭(但是,我记得与此相关的某些 GCC 版本存在一些问题)或只需将您的代码拆分为单独的文件并使用不同的标志编译它们。当然,如果您可以隔离可能出现无穷大和 NaN 的部分,这也适用于更一般的设置。如果您无法隔离这些部分,则强烈表明您不能用于此代码。-ffast-math
-ffinite-math-only
-fno-finite-math-only
-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
.