10

示例代码:

#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

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

与,和-ffast-math的结果是什么。它是否变得未定义,还是应该是 NaN / -Inf?std::sqrt(-1.0)std::log(0.0)

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

4

1 回答 1

18

请注意,这-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任何调用来优化代码(并进一步优化从那里)。如果你违背了对编译器的承诺,编译器不需要创建正确的程序。isinfisnanfalse

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它仍然会提供一些优化。

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

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

在这种情况下,您可以拆分代码并使用 optimize#pragma或(分别和)为给定的代码段选择性地__attribute__打开和关闭(但是,我记得与此相关的某些 GCC 版本存在一些问题)或只需将您的代码拆分为单独的文件并使用不同的标志编译它们。当然,如果您可以隔离可能出现无穷大和 NaN 的部分,这也适用于更一般的设置。如果您无法隔离这些部分,则强烈表明您不能用于此代码。-ffast-math-ffinite-math-only-fno-finite-math-only-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.

于 2014-04-08T08:17:36.603 回答