为什么使用 double 然后转换为 float?

Why using double and then cast to float?

我正在努力提高 surf.cpp 性能。从第140行可以找到这个函数:

inline float calcHaarPattern( const int* origin, const SurfHF* f, int n )
{
    double d = 0;
    for( int k = 0; k < n; k++ )
        d += (origin[f[k].p0] + origin[f[k].p3] - origin[f[k].p1] - origin[f[k].p2])*f[k].w;
    return (float)d;
}

运行 Intel Advisor 向量化分析,它表明“存在 1 个数据类型转换”可能效率低下(尤其是在向量化中)。

但我的问题是:看看这个函数,为什么作者会创建 d 作为 double 然后将其转换为 float?如果他们想要十进制数,float 就可以了。我想到的唯一原因是,由于 doublefloat 更精确,因此它可以表示更小的数字,但最终值足够大,可以存储在 float 中,但我没有 运行 对 d 值进行任何测试。

还有其他可能的原因吗?

因为作者在计算的时候想要精度更高,所以只对最后的结果进行了四舍五入。这与在计算过程中保留更多有效位相同。

更确切地说,加减法时,误差是可以累积的。当涉及大量浮点数时,此错误可能相当大。

你质疑的答案是在求和时使用更高的精度,但我不明白为什么。这个答案是正确的。考虑这个带有完整 made-up 个数字的简化版本:

#include <iostream>
#include <iomanip>

float w = 0.012345;

float calcFloat(const int* origin, int n )
{
    float d = 0;
    for( int k = 0; k < n; k++ )
        d += origin[k] * w;
    return (float)d;
}

float calcDouble(const int* origin, int n )
{
    double d = 0;
    for( int k = 0; k < n; k++ )
        d += origin[k] * w;
    return (float)d;
}


int main()
{
  int o[] = { 1111, 22222, 33333, 444444, 5555 };
  std::cout << std::setprecision(9) << calcFloat(o, 5) << '\n';
  std::cout << std::setprecision(9) << calcDouble(o, 5) << '\n';
}

结果是:

6254.77979
6254.7793

因此,即使两种情况下的输入相同,使用 double 进行中间求和也会得到不同的结果。将 calcDouble 更改为使用 (double)w 不会更改输出 .

这表明 (origin[f[k].p0] + origin[f[k].p3] - origin[f[k].p1] - origin[f[k].p2])*f[k].w 的计算是 high-enough 精度,但他们试图避免在求和过程中累积误差。

这是因为在处理浮点数时错误是如何传播的。引用 The Floating-Point Guide: Error Propagation:

In general:

  • Multiplication and division are “safe” operations
  • Addition and subtraction are dangerous, because when numbers of different magnitudes are involved, digits of the smaller-magnitude number are lost.

因此您需要 higher-precision 类型的总和,这涉及加法。将整数乘以 double 而不是 float 几乎没有那么重要:你会得到与你开始的 float 值大致一样准确的东西(只要结果它不是非常非常大或非常非常小)。但是,将可能具有非常不同数量级的 float 值相加,即使单个数字本身可以表示为 float,也会累积错误并越来越偏离真实答案。

要查看实际效果:

float f1 = 1e4, f2 = 1e-4;
std::cout << (f1 + f2) << '\n';
std::cout << (double(f1) + f2) << '\n';

或等价地,但更接近原始代码:

float f1 = 1e4, f2 = 1e-4;
float f = f1;
f += f2;
double d = f1;
d += f2;
std::cout << f << '\n';
std::cout << d << '\n';

结果是:

10000                                                                                                                                                                                                             
10000.0001   

将两个浮点数相加会失去精度。将 float 添加到 double 会给出正确的答案,即使输入是相同的。您需要九个有效数字来表示正确的值,这对于 float.

来说太多了