使用 quiet_NaN() 和 -Ofast 时检测不到 NaN

Undetectable NaN when using quiet_NaN() and -Ofast

我正在用 OpenCV 编写一个经典的 nanmean 函数。 我尝试通过默认行为模拟 MatLab 的 nanmean(即 nanmean 在第一维上减少)。 我生成了一个随机大小的矩阵,可以是 CV_32FCV_64F,最多有 4 个通道。我按照统一的法则用随机值填充它。 然后我使用 std::numerical_limits<float> 为 Nan 分配一些值(如果 CV_32F,否则 double:: 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;

T是模板类型,可以是float或者double,没有别的。

输出为:

v before: nan 0 0 1 0 1

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

我有几个问题:


#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;

 }

您在评论中提到您正在使用 -Ofast,这就是导致问题的原因。要理解这是为什么,我们首先要查看 GCC documentation for options that control optimizations。此处列出了 -Ofast 打开的以下选项:

It turns on -ffast-math, -fallow-store-data-races and the Fortran-specific -fstack-arrays, unless -fmax-stack-var-size is specified, and -fno-protect-parens.

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

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.

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

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

This option is not turned on by any -O option 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. [emphasis added]

由于您的用例明确包括检测 NaN,因此似乎ill-advised 使用此标志。

使用 -Ofast 选项 (Example on Godbolt because I don't have access to GCC right now) 使用 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");
    }
}