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

C++ std::isinf不能与-ffast math一起使用。如何检查无穷大,c++,c++-standard-library,fast-math,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 m

示例代码:

#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;
}
#包括
#包括
#包括
使用名称空间std;
静态布尔值my_isnan(双val){
并{double f;uint64_t x;}u={val};
返回(u.x 0x7FF0000000U;
}
int main(){

cout注意,
-ffast math
可能会使编译器忽略/违反IEEE规范,请参阅:

除了-Ofast之外,任何-O选项都不会启用此选项,因为它 可能会导致依赖于精确表达式的程序输出不正确 数学函数IEEE或ISO规则/规范的实施。 但是,它可能会为那些不需要代码的程序生成更快的代码 这些规范的保证

因此,使用
-ffast math
并不能保证在应该看到的地方看到无穷大

特别是,
-ffast math
仅启用
-ffinite math
,请参见其中的含义(从)

[…]假定参数和结果不是NAN或+-INF的浮点算术优化

这意味着,通过启用
-ffast math
,您向编译器承诺您的代码永远不会使用无穷大或NaN,这反过来允许编译器优化代码,例如,将对
isinf
isnan
的任何调用替换为常量
false
(并从那里进一步优化).如果您违背了对编译器的承诺,编译器不需要创建正确的程序

因此答案很简单,如果您的代码可能有无穷大或NaN(这一点由您使用
isinf
isnan
这一事实强烈暗示),则无法启用
-ffast math
,否则您可能会得到错误的代码

您对my_isnan的实现(在某些系统上)是有效的,因为它直接检查浮点数的二进制表示形式。当然,处理器仍可能进行(某些)实际计算(取决于编译器进行的优化),因此实际的NaN可能会出现在内存中,您可以检查它们的二进制表示形式,但如上所述,
std::isnan
可能已被常量
false
所取代。编译器也可能会用一些甚至不为输入
生成NaN的版本来代替,例如,
sqrt
-1
。要查看编译器进行了哪些优化,请编译到汇编程序并查看该代码

为了做出一个(不是完全无关的)类比,如果你告诉编译器你的代码是C++,你不能期望它正确编译C代码,反之亦然(有实际的例子,例如)。 启用

-ffast math
并使用
my_isnan
是一个坏主意,因为这将使一切都非常依赖于机器和编译器。您不知道编译器总体上做了哪些优化,因此可能存在与使用非有限数学相关的其他隐藏问题,但会告诉编译器其他情况

一个简单的修复方法是使用
-ffast math-fno finite math only
,这仍然会提供一些优化

也可能是您的代码如下所示:

  • 过滤掉所有的无穷和非
  • 对过滤后的值做一些有限数学(我指的是保证永远不会产生无穷大或N的数学,必须非常非常仔细地检查)
  • 在这种情况下,您可以拆分代码,并使用optimize
    #pragma
    uuu属性uuu
    为给定代码段选择性地打开或关闭
    -ffast math
    (分别为
    -ffinite math only
    -fno finite math only
    )(然而,我记得与此相关的GCC版本存在一些问题)或者只需将代码拆分为单独的文件,并使用不同的标志编译它们。当然,如果可以隔离可能出现无穷大和N的部分,这也适用于更一般的设置。如果无法隔离这些部分,这强烈表明您不能对此代码使用
    -ffinite math only

    最后,重要的是要理解,
    -ffast math
    不是一种无害的优化,它只会使程序更快。它不仅影响代码的性能,而且影响代码的正确性(如果我还记得right的主页上有一系列恐怖故事的话,这是所有关于浮点数的问题中最重要的一个。简言之,你可能会得到更快的代码,但也会得到错误或意外的结果(见下面的示例)。因此,只有当您真正了解自己在做什么,并且您已经完全确定这两种情况时,才应该使用此类优化

  • 优化不会影响特定代码的正确性,或者
  • 优化引入的错误对代码来说并不重要
  • 根据是否使用此优化,程序代码的行为实际上可能完全不同。特别是当启用优化(如
    -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;
    }
    
    #包括
    #包括
    int main(){
    双d=1.0;
    double max=std::numeric_limits::max();
    d/=最大值;
    d*=最大值;
    
    std::我想这可能取决于编译器,也可能取决于您使用的操作系统。在OSX上使用
    clang
    5.1,您的示例代码将打印
    1
    @SylvainDefresne:是的,但问题主要是:如何以始终有效的方式进行检查?打印
    std::log(0.0)可能会有所帮助
    查看
    std::log
    std::isinf
    是否计算了错误的结果。我对我的问题进行了一点扩展。为什么
    my_isnan
    有效,而
    std::isnan
    无效?那么,接下来的问题是:
    -ffast math
    的计算结果是什么