我正在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 是模板类型,它可以是float或double,不能是其他。
输出是:
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)。
我有几个问题:
#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)
您在评论中提到您正在使用-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,因此使用此标志似乎是不明智的。
使用选项-Ofast(Godbolt 上的示例,因为我现在无法访问 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)