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

13

示例代码:

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

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,该代码将打印“0, 0, 1, 1”,否则将打印“1, 1, 1, 1”。

这正确吗?我认为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-使用isNaN的性能提高机会


我认为这将取决于您使用的编译器和可能的操作系统。在OS X上使用clang 5.1,您的示例代码将打印1 - Sylvain Defresne
@SylvainDefresne:是的,但问题主要是:我该如何以一种始终有效的方式进行检查? - Albert
也许打印 std::log(0.0) 会有帮助,以查看是 std::log 还是 std::isinf 计算了错误的结果。 - microtherion
1
你的 my_isnan 不正确:my_isnan(45.0) == 1 - Jean-Michaël Celerier
@Jean-MichaëlCelerier 嗯,是的。我不知道这是从哪里来的。我认为它应该是(u.x << 1) > (0x7ff0000000000000u << 1) - Albert
显示剩余4条评论
1个回答

20
请注意,-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,从而允许编译器通过将对 isinfisnan 的任何调用替换为常量 false(并进一步进行优化)来优化代码。如果您违反了对编译器的承诺,则编译器不需要创建正确的程序。
因此,答案很简单,如果您的代码可能具有无限大或NaN(这在您使用isinfisnan的事实强烈暗示),则不能启用-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,这样仍然会给出一些优化。
也许你的代码看起来像这样:
  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;
}

不使用任何优化标志编译时,将输出预期的1;但使用-ffast-math标志编译时,将输出0


我稍微扩展了一下我的问题。为什么 my_isnan 起作用,而 std::isnan 没有起作用呢? - Albert
@Albert:它会恢复到标准状态,即未定义行为 - 任何事情都可能发生。“未指定”太强了。 - MSalters
这是未定义的,因为使用 std::sqrt(-1.0) 会违反只使用有限值的要求,即没有无穷大或 NaN。编译器可能仍然为 sqrt 生成“标准”代码,并且实际计算产生表示为二进制的 -inf,但也可能发生编译器使用某些无法处理输入 -1 的不同实现的情况。事实上,我的 GCC 版本在启用 -ffast-math 时生成 call __sqrt_finite 而不是 call sqrt(后者似乎像前者一样工作,但这并非必然如此)。 - godfatherofpolka
__isnan__isinf怎么样?它们不应该总是有效吗? - Albert
@Albert:为什么它们应该工作?如果你需要使用__isnan__isinf,那么你不仅仅是在使用有限的数学,因此你不能启用-ffast-math(除非当然,你喜欢被你的代码惊吓...)。当然,你可能可以编写一些自定义的isnan等函数,在特定给定的编译器版本和平台上仍然能够在存在-fast-math的情况下实现你想要的功能,但这样做是极其糟糕的做法,因为你的代码可能会在下一个编译器更新中出现问题。 - godfatherofpolka
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接