我正在努力改善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
就可以了。我想到的唯一原因是,由于double
比float
更精确,因此它可以代表较小的数字,但最终值足够大,可以存储在float
中,但我没有对d
值进行任何测试。
还有其他可能的原因吗?
答案 0 :(得分:7)
因为作者想要在计算过程中获得更高的精度,所以只能围绕最终结果。这与在计算过程中保留更多有效数字相同。
更确切地说,当加法和减法时,可以累积误差。当涉及大量浮点数时,此错误可能相当大。
答案 1 :(得分:4)
你质疑答案是说在总结期间使用更高的精确度,但我不明白为什么。答案是对的。考虑这个带有完全编号的简化版本:
#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
的计算精度足够高,但求和期间误差的积累是他们试图避免的。
这是因为在处理浮点数时如何传播错误。引用The Floating-Point Guide: Error Propagation:
一般来说:
- 乘法和除法是“安全”操作
- 加法和减法是危险的,因为当涉及不同大小的数字时,较小幅度数字的数字会丢失。
所以你想要总和的更高精度类型,这涉及添加。将整数乘以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
来说太多了。