使用 Quiet_NaN() 和 -Ofast 时无法检测到 NaN

Joh*_*318 5 c++ nan

我正在nanmean用 OpenCV 编写一个经典函数。我尝试模拟 MatLab 的nanmean默认行为(即nanmean在第一维上减少)。CV_32F我生成一个随机大小的矩阵,最多可以有CV_64F4 个通道。我按照统一法则用随机值填充它。然后我使用std::numerical_limits<float>(if CV_32F, doubleelse)为 Nan 分配一些值:: quiet_NaN()

在调试步骤中,我正在寻找问题并打印以下内容:

T v = *it_src;
std::cout<<"v before: "<<v<<" "<<std::isnan(v)<<" "<<cvIsNaN(v)<<" "<<(v==v)<<" "<<" "<<std::isinf(v)<<" "<<((v+v)==v)<<std::endl;
Run Code Online (Sandbox Code Playgroud)

T 是模板类型,它可以是floatdouble,不能是其他。

输出是:

v before: nan 0 0 1 0 1
Run Code Online (Sandbox Code Playgroud)

所以该值是“nan”,但“std::isnan”和“CvIsNan”都无法检测到它。IEEE 754 的比较功能(如果 v 是 Nan 则v == v应该为 false)失败(v == v返回true)。唯一有效的是最后一个检查 ( (v+v) == v)。

我有几个问题:

  • 简单来说为什么?
  • 这个问题从何而来以及如何解决?
  • SIMD指令也与此有关吗?

#include <opencv2/core.hpp>
#include <iostream>

using namespace cv;

int main()
{
    // Initialization
    int rows(theRNG().uniform(10,21)), cols(theRNG().uniform(10,21));
    int cn(theRNG().uniform(1, 5)); // [1, 5[ -> [1,4]
    
    Mat src(rows, cols, CV_32FC(cn));
    
    theRNG().fill(src, RNG::UNIFORM, 0, 10000);
    
    float ratio = theRNG().uniform(0.1f, 0.8f);
    int nb_points = saturate_cast<int>(src.total() * ratio);
    
    for(int i=0;i<nb_points;i++)
    {
     int x = theRNG().uniform(0, cols);
     int y = theRNG().uniform(0, rows);
     int z = theRNG().uniform(0, cn);
    
     src.ptr<float>(y,x)[z] = std::numeric_limits<float>::quiet_NaN();
    
    }
    
    
    Mat dst = Mat::zeros(1, cols, src.type());
    
    // Computation (reduce mean with omition of the Nan value over the first axis).
    const size_t src_step1 = src.step1();
    
    for(int c=0; c<cols; c++)
    {
        // The default constructor of Scalar_ initialize the elements of the attribute "val" to 0.
        Scalar sum;
        Scalar_<int> cnt;
    
        const float* it_src = src.ptr<float>(0,c);
    
        for(int r=0; r<rows; r++, it_src+=src_step1)
            for(int i=0;i<cn;i++)
            {
                float v = it_src[i];
    
                std::cout<<"v before: "<<v<<" "<<std::isnan(v)<<" "<<cvIsNaN(v)<<" "<<(v==v)<<" "<<" "<<std::isinf(v)<<" "<<((v+v)==v)<<std::endl;
    
                if(!std::isnan(v)) // Failing
                {
                    sum[i]+= saturate_cast<double>(v);
                    cnt[i]++;
                }
            }
    
        for(int i=0; i<cn;i++)
        {
            float den = saturate_cast<float>(cnt[i]);
            if(den==0.f)
                den = 1.f;
            dst.ptr<float>(0, c)[i] = saturate_cast<float>(sum[i]) / den;
        }
    }

 return 0;

 }
Run Code Online (Sandbox Code Playgroud)

Jac*_*cob 5

您在评论中提到您正在使用-Ofast,这就是导致问题的原因。为了理解为什么会这样,我们首先查看GCC 文档中控制优化的选项。这里列出了由 开启的以下选项-Ofast

它打开 -ffast-math、-fallow-store-data-races 和 Fortran 特定的 -fstack-arrays(除非指定 -fmax-stack-var-size)和 -fno-protect-parens。

对于您的情况,查看 的描述-ffast-math似乎相关,因为它设置了以下选项集:

设置选项 -fno-math-errno、-funsafe-math-optimizations、-ffinite-math-only、-fno-rounding-math、-fno-signaling-nans、-fcx-limited-range 和 -fexcess- precision=快速地。

我没有查看所有这些选项的作用,但-ffinite-math-only具体来说它似乎与任何关心 NaN 或 Inf 的程序不兼容,因为它被记录为:

允许对假设参数和结果不是 NaN 或 +-Infs 的浮点算术进行优化。

任何 -O 选项都不会打开此选项,因为它可能会导致依赖于数学函数的 IEEE 或 ISO 规则/规范的精确实现的程序输出不正确。然而,对于不需要这些规范保证的程序,它可能会产生更快的代码。[强调]

由于您的用例明确包括检测 NaN,因此使用此标志似乎是不明智的。

使用选项-OfastGodbolt 上的示例,因为我现在无法访问 GCC)使用 GCC 11.3 进行编译时,演示了该问题的简单示例程序。没有-O3按预期打印任何内容,并-Ofast打印Brought to you by -Ofast

#include <limits>
#include <cstdio>
#include <cmath>

int main() {
    auto value = std::numeric_limits<float>::quiet_NaN();

    if(!std::isnan(value)) {
        std::puts("Brought to you by -Ofast");
    }
}
Run Code Online (Sandbox Code Playgroud)