示例代码:
#include <iostream>
#include <cmath>
#include <stdint.h>
using namespace std;
static bool my_isnan(double val) {
union { double f; uint64_t x; } u = { val };
return (u.x << 1) > 0x7ff0000000000000u;
}
int main() {
cout << std::isinf(std::log(0.0)) << endl;
cout << std::isnan(std::sqrt(-1.0)) << endl;
cout << my_isnan(std::sqrt(-1.0)) << endl;
cout << __isnan(std::sqrt(-1.0)) << endl;
return 0;
}
使用-ffast-math
,该代码打印&#34; 0,0,1,1和#34; - 没有,它打印&#34; 1,1,1,1&#34;。
这是对的吗?在这些情况下,我认为std::isinf
/ std::isnan
仍应与-ffast-math
一起使用。
另外,如何使用-ffast-math
检查无限/ NaN?您可以看到my_isnan
这样做,它确实有效,但该解决方案当然非常依赖于架构。另外,为什么my_isnan
在这里工作而std::isnan
没有?那么__isnan
和__isinf
怎么样?他们总是工作吗?
使用-ffast-math
,std::sqrt(-1.0)
和std::log(0.0)
的结果是什么?它是否未定义,或者应该是NaN / -Inf?
相关讨论:(GCC) [Bug libstdc++/50724] New: isnan broken by -ffinite-math-only in g++,(Mozilla) Bug 416287 - performance improvement opportunity with isNaN
答案 0 :(得分:13)
请注意,-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或+ -Infs
这意味着,通过启用-ffast-math
,您向编译器承诺您的代码永远不会使用无穷大或NaN,这反过来允许编译器通过例如替换对{的任何调用来优化代码常数isinf
{1}}或isnan
(并从那里进一步优化)。如果您违反了对编译器的承诺,则编译器不需要创建正确的程序。
因此,答案非常简单,如果您的代码可能有无穷大或NaN(由于您使用false
和isinf
强烈暗示),则无法启用isnan
否则你可能得到错误的代码。
-ffast-math
的实现(在某些系统上)有效,因为它直接检查浮点数的二进制表示。当然,处理器仍然可以进行(某些)实际计算(取决于编译器执行的优化),因此实际NaN可能出现在内存中,您可以检查它们的二进制表示,但如上所述,my_isnan
可能已被常量std::isnan
取代。同样可能发生的是,编译器替换了某些版本,例如false
,甚至没有为输入sqrt
生成NaN。为了看看编译器做了哪些优化,请编译成汇编程序并查看该代码。
要进行(并非完全不相关)类比,如果您告诉编译器您的代码是C ++,您不能指望它正确编译C代码,反之亦然(有实际的例子,例如Can code that is valid in both C and C++ produce different behavior when compiled in each language?)。
启用-1
并使用-ffast-math
是一个坏主意,因为这会使所有内容都依赖于机器和编译器,你不知道编译器的整体优化程度,所以可能是与您使用非有限数学相关的其他隐藏问题,但是告诉编译器。
一个简单的解决方法是使用my_isnan
,这仍然可以进行一些优化。
也可能是您的代码看起来像这样:
在这种情况下,您可以拆分代码并使用优化-ffast-math -fno-finite-math-only
或#pragma
转换__attribute__
(分别为-ffast-math
和-ffinite-math-only
)并且选择性地关闭给定的代码片段(但是,我记得有些版本的GCC与此相关有问题)或者只是将代码拆分成单独的文件并用不同的标志编译它们。当然,如果您可以隔离可能出现无穷大和NaN的部分,这也适用于更一般的设置。如果您无法隔离这些部分,则表明您无法将-fno-finite-math-only
用于此代码。
最后,了解-ffinite-math-only
并非无害的优化只会让您的程序更快,这一点非常重要。它不仅会影响代码的性能,还会影响其正确性(而且除了浮点数之外的所有问题,如果我还记得,William Kahan在他的主页上有一系列恐怖故事,请参阅What every programmer should know about floating point arithmetic)。简而言之,您可能会获得更快的代码,但也会出现错误或意外的结果(请参阅下面的示例)。因此,当你真正知道自己在做什么并且你已经完全确定时,你应该只使用这样的优化
程序代码的实际行为可能完全不同,具体取决于是否使用此优化。特别是当启用-ffast-math
等优化时,它可能会出现错误(或至少与您的期望非常相反)。以下面的程序为例:
-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
。