我尝试计算浮点数组的平方和。
如何减少舍入误差?
我想在实际程序的内循环中总计大约5,000,000个浮点数。
TEST.CPP:
#include <iostream>
#include <stdint.h>
template <typename Sum, typename Element>
Sum sum(const size_t st, const size_t en) {
Sum s = 0;
for (size_t i = st; i < en; ++ i) {
s += Element(i)*Element(i);
}
return s;
}
int main() {
size_t size = 100000;
std::cout << "double, float: "
<< sum<double, float>(0,size) << "\n";
std::cout << "int, int: "
<< sum<int, int>(0,size) << "\n";
}
输出:
double, float: 3.33328e+14
int, int: 216474736
答案 0 :(得分:2)
如果已知浮点数的格式,例如IEEE,则可以使用由浮点数的指数索引的数组来存储部分和,然后求和以生成总和。在阵列更新期间,只有具有相同指数的浮点数一起添加并存储到适当位置的数组中。最终总和从最小到最大。对于C ++,数组和函数可以是类的成员。
将数组作为参数传递给函数的浮点数示例:
/* clear array */
void clearsum(float asum[256])
{
size_t i;
for(i = 0; i < 256; i++)
asum[i] = 0.f;
}
/* add a number into array */
void addtosum(float f, float asum[256])
{
size_t i;
while(1){
/* i = exponent of f */
i = ((size_t)((*(unsigned int *)&f)>>23))&0xff;
if(i == 0xff){ /* max exponent, could be overflow */
asum[i] += f;
return;
}
if(asum[i] == 0.f){ /* if empty slot store f */
asum[i] = f;
return;
}
f += asum[i]; /* else add slot to f, clear slot */
asum[i] = 0.f; /* and continue until empty slot */
}
}
/* return sum from array */
float returnsum(float asum[256])
{
float sum = 0.f;
size_t i;
for(i = 0; i < 256; i++)
sum += asum[i];
return sum;
}
答案 1 :(得分:2)
对int
使用Element
类型时,i
之后的每个std::sqrt(std::numeric_limits<int>::max())
广场都会出现溢出,可能是46341
你的系统。
当总和到达std::numeric_limits<int>::max()
时,总和上也有溢出。
您可以使用long
或long long
代替int
来增加此数字。
在乘法之前将第一个float
存储或转换为double
或long double
也是一个好主意,以减少浮点平方操作的误差。
舍入一组计算的最后几步总是比舍入早期步骤更好,因为您避免在内部计算上传播(并增加)表示错误。
如果你真的想要精确,并且不想使用一些复杂的技术重新发明轮子,你可以使用像GNU Multi-Precision Library
或Boost Multiprecision
这样的多精度库:
https://en.wikipedia.org/wiki/List_of_arbitrary-precision_arithmetic_software
它们比您系统的long double
类型更准确
答案 2 :(得分:2)
如果您只想添加连续值的方块,请使用公式n*(n+1)*(2n+1)/6
计算从1
到n
的所有值的平方和。
只要使用可以表示结果的类型,就可以消除舍入的大多数影响。例如;
template<typename Sum> Sum sumsq(size_t n)
{
// calculates sum of squares from 1 to x
// assumes size_t can be promoted to a Sum
Sum temp(n); // force promotion to type Sum
return temp * (temp + 1)* (2*temp + 1)/6;
}
template<typename Sum> Sum alternate_sum(size_t st, size_t en)
{
Sum result = sumsq(en - 1);
if (st > 0) result -= sumsq(st-1);
return result;
}
int main()
{
size_t size = 100000;
std::cout << "double, float: "
<< alternate_sum<double>(0,size) << "\n";
std::cout << "int, int: "
<< alternate_sum<long long>(0,size) << "\n";
}
请注意,对于size
等于100000
,使用int
保存结果会产生未定义的行为(有符号整数类型的溢出)。
-1
中的alternate_sum()
反映您的循环格式为for (size_t i = st; i < en; ++ i)
您可以取消使用size_t
类型作为固定功能,但我会将其作为练习。
BTW:既然你说这段代码是在内循环中,那么值得注意的是,这个公式将比你一直使用的循环快得多。
答案 3 :(得分:2)
一个浮点数有24个有效位,而一个浮点数有53个。所以你有29个保护位,大约是5000000的100倍。
因此,只有比最大值小100倍的值才会出现舍入误差。
另请注意,在英特尔架构中,浮点寄存器实际上保持80位的扩展精度数,其中63位是重要的。
然后,只有小于最大值100000倍的数字才会发生截断。
你真的担心吗?