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

Alb*_*ert 5 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;
}
Run Code Online (Sandbox Code Playgroud)

在线编译器.

使用-ffast-math,该代码打印"0,0,1,1" - 没有,它打印"1,1,1,1".

那是对的吗?在这些情况下,我认为std::isinf/ std::isnan应该仍然可以使用-ffast-math.

另外,我如何检查无限/ NaN -ffast-math?您可以看到my_isnan这样做,它确实有效,但该解决方案当然非常依赖于架构.另外,为什么在my_isnan这里工作而std::isnan不是?怎么样__isnan__isinf.他们总是工作吗?

随着-ffast-math,什么是结果std::sqrt(-1.0)std::log(0.0).它是不确定的,还是应该是NaN/-Inf?

相关讨论:(GCC)[Bug libstdc ++/50724]新:isnan在g ++中被-ffinite-math-broken破坏,(Mozilla)Bug 416287 - isNaN的性能提升机会

god*_*lka 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#优化选项)

[...]浮点运算的优化,假设参数和结果不是NaN或+ -Infs

这意味着,通过使-ffast-math你做出一个承诺,你的代码将永远不会使用无限或NaN,从而允许编译器优化,例如,通过更换任何调用的代码编译器isinfisnan由常数false(并进一步从优化那里).如果您违反了对编译器的承诺,则编译器不需要创建正确的程序.

因此答案很简单,如果你的代码可能有无穷大或NaN(这是你使用isinf和强烈暗示的isnan),你不能启用-ffast-math,否则你可能会得到错误的代码.

您的my_isnan工作实现(在某些系统上),因为它直接检查浮点数的二进制表示.当然,处理器仍然可以进行(某些)实际计算(取决于编译器执行的优化),因此实际的NaN可能出现在内存中,您可以检查它们的二进制表示,但如上所述,std::isnan可能已被替换为不变的false.同样可能发生的是,编译器例如sqrt通过某些甚至不产生用于输入的NaN的版本来替换-1.为了查看编译器执行的优化,请编译成汇编程序并查看该代码.

要做一个(不完全不相关)的类比,如果你告诉你的编译器你的代码是用C++编写的,你不能指望它正确地编译C代码,反之亦然(有一些实际的例子,例如可以有效的代码)在C和C++中,当用每种语言编译时会产生不同的行为吗?).

启用-ffast-math和使用是一个坏主意,my_isnan因为这将使一切都依赖于机器和编译器,你不知道编译器总体上做了什么优化,所以可能存在与你使用非事实有关的其他隐藏问题有限数学但是告诉编译器.

一个简单的解决方法是使用-ffast-math -fno-finite-math-only哪个仍然会进行一些优化.

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

  1. 过滤掉所有无穷大和NaN
  2. 对过滤后的值做一些有限的数学运算(我的意思是保证永远不会产生无穷大或NaN的数学,这必须经过非常非常仔细的检查)

在这种情况下,你可能会分散你的代码,并可以使用优化#pragma__attribute__-ffast-math(分别-ffinite-math-only-fno-finite-math-only上)和关闭选择性的代码给定片(不过,我记得有被一些麻烦一些版本的GCC的与此相关的)或只需将代码拆分为单独的文件,然后使用不同的标志进行编译.当然,如果您可以隔离可能出现无穷大和NaN的部分,这也适用于更一般的设置.如果您无法隔离这些部件,则表明您无法使用-ffinite-math-only此代码.

最后,重要的是要理解这-ffast-math不是一种无害的优化,只会简化程序.它不仅会影响你的代码的性能,还会影响它的正确性(而且除了浮点数之外的所有问题,如果我还记得正确的William Kahan在他的主页上有一系列恐怖故事,请参阅每个程序员的内容应该知道浮点运算).简而言之,您可能会获得更快的代码,但也会出现错误或意外的结果(请参阅下面的示例).因此,当你真正知道自己在做什么并且你已经完全确定时,你应该只使用这样的优化

  1. 优化不会影响特定代码的正确性,或者
  2. 优化引入的错误对代码并不重要.

根据是否使用此优化,程序代码实际上可能表现完全不同.特别是当-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;
}
Run Code Online (Sandbox Code Playgroud)

1在没有任何优化标志的情况下编译时会产生预期的输出,但是使用-ffast-math它会输出0.