isnan 不能与 `-Ofast` 标志一起正常工作

isnan does not work correctly with `-Ofast` flags

考虑以下测试程序:

# include <gsl/gsl_statistics_double.h>
# include <iostream>

using namespace std;

int main()
{
  double y = 50.2944, yc = 63.2128;
  double pearson_corr = gsl_stats_correlation(&y, 1, &yc, 1, 1);
  cout << "pearson_corr = " << pearson_corr << endl;
  if (isnan(pearson_corr))
    cout << "It is nan" << endl;
  else
    cout << "Not nan" << endl;
}

在某种程度上,这段代码有些荒谬,但它的目的是展示我遇到的一个细微错误。

调用 gsl_stats_correlation() 应该会出错,因为样本数为 1,并且皮尔逊系数至少对两个样本有意义。

当我这样编译时:

c++ test-r2.cc -lgsl -lgslcblas

程序打印出 -nan 作为结果和消息 "It is nan",我认为这是正确的,因为正如我所说,无法计算系数。对 isnan() 的调用正确检测到结果是 nan。但是,当我这样编译时:

c++ -Ofast test-r2.cc -lgsl -lgslcblas

程序打印出 -nan 作为结果,但消息 "Not nan",这表明对 isnan() 的调用无法检测到 pearson_corr 变量的无效性。

所以,我的第一个问题是“为什么使用 -Ofast 标志调用 isnan() 无法检测到变量是 nan。我的第二个问题是如何以独立于给编译器的优化标志的方式解决这个问题?

我在 ubuntu 16.04 上使用 gnu c++ 5.4.0 版,在 64 位模式下使用 Intel i5 运行

提前致谢

man gcc 是这样说的:

   -Ofast
       Disregard strict standards compliance.  -Ofast enables all -O3 optimizations.  It also enables optimizations that are not valid for all standard-compliant programs.  It turns on -ffast-math
       and the Fortran-specific -fno-protect-parens and -fstack-arrays.

还有这个:

   -ffast-math
       Sets -fno-math-errno, -funsafe-math-optimizations, -ffinite-math-only, -fno-rounding-math, -fno-signaling-nans and -fcx-limited-range.

       This option causes the preprocessor macro "__FAST_MATH__" to be defined.

       This option is not turned on by any -O option besides -Ofast since it can result in incorrect output for programs that depend on an exact implementation of IEEE or ISO rules/specifications
       for math functions. It may, however, yield faster code for programs that do not require the guarantees of these specifications.

您是在告诉编译器忽略 NaN 以及其他奇怪的浮点数族。如果您不希望编译器这样做,请告诉它 -O2-O3

-Ofast,除其他外,激活 GCC 的 -ffast-math 模式,-ffast-math,除其他外,导致编译器生成假定 NaN(和 Inf)从不发生。

因此,如果您需要使用 NaN 或 Inf,则不得使用-Ofast。没有解决方法。

无论如何,-O2 -march=native 几乎所有程序都能更好地服务; -Ofast 打开极其激进的内联和循环展开,根据我的经验,几乎总是会破坏 I-cache 并使程序变慢

您告诉 GCC 假设没有 NaN,所以它假设没有 NaN。如果这样做,任何依赖于 NaN 的代码显然都不可靠。

来自docs

  • -Ofast

    Disregard strict standards compliance. -Ofast enables all -O3 optimizations. It also enables optimizations that are not valid for all standard-compliant programs. It turns on -ffast-math and the Fortran-specific -fstack-arrays, unless -fmax-stack-var-size is specified, and -fno-protect-parens.

  • -ffast-数学

    Sets the options -fno-math-errno, -funsafe-math-optimizations, -ffinite-math-only, -fno-rounding-math, -fno-signaling-nans, -fcx-limited-range and -fexcess-precision=fast. ...

  • -仅有限数学

    Allow optimizations for floating-point arithmetic that assume that arguments and results are not NaNs or +-Infs.

-Ofast

Disregard strict standards compliance. -Ofast enables all -O3 optimizations. It also enables optimizations that are not valid for all standard-compliant programs. It turns on -ffast-math

在这种情况下 isnan 不起作用,如此处更好地解释 Mingw32 std::isnan with -ffast-math

(其中一个答案包含一个可行的实现,但也可以将 isnan 包装在未使用 -Ofast 编译的 c 文件中)

您可以使用此选项:-Ofast -fno-finite-math-only,因此 isnan 仍然有效。

或者,如果您不想使用 -fno-finithe-math-only,但仍想检测 NaN 数字,您可以通过依赖于平台的方式进行:实现您自己的 isnan 函数。

例如,如果您使用的是 32 位浮点数和 64 位双精度数的 IEEE-754 平台,您可以:

#include <cstdint>
#include <string.h>

bool myIsnan(float v) {
    std::uint32_t i;
    memcpy(&i, &v, 4);
    return ((i&0x7f800000)==0x7f800000)&&(i&0x7fffff);
}

bool myIsnan(double v) {
    std::uint64_t i;
    memcpy(&i, &v, 8);
    return ((i&0x7ff0000000000000)==0x7ff0000000000000)&&(i&0xfffffffffffff);
}