std :: isinf不能与-ffast-math一起使用。如何检查无穷大

时间:2014-04-08 08:07:05

标签: c++ c++-standard-library fast-math

示例代码:

#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;
}

Online compiler

使用-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-mathstd::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

1 个答案:

答案 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(由于您使用falseisinf强烈暗示),则无法启用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,这仍然可以进行一些优化。

也可能是您的代码看起来像这样:

  1. 过滤掉所有无穷大和NaN
  2. 对过滤后的值做一些有限的数学运算(我的意思是保证永远不会产生无穷大或NaN的数学,这必须非常仔细地检查)
  3. 在这种情况下,您可以拆分代码并使用优化-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)。简而言之,您可能会获得更快的代码,但也会出现错误或意外的结果(请参阅下面的示例)。因此,当你真正知道自己在做什么并且你已经完全确定时,你应该只使用这样的优化

    1. 优化不会影响特定代码的正确性,或
    2. 优化引入的错误对代码并不重要。
    3. 程序代码的实际行为可能完全不同,具体取决于是否使用此优化。特别是当启用-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