如果接近零值非常接近零,你就会有一个四舍五入的问题的总和(可以舍入误差向上或向下),或者如果总结大组数字的任何数值范围。解决此问题的一个方法是使用求和函数,该求和函数仅添加具有相同指数的数字(直到您调用getsum()以获取总和,并使指数尽可能接近)。示例C++类来执行此操作(注意代码是使用Visual Studio编译的,在uint64_t可用之前编写)。
// SUM contains an array of 2048 IEEE 754 doubles, indexed by exponent,
// used to minimize rounding/truncation issues when doing
// a large number of summations
class SUM{
double asum[2048];
public:
SUM(){for(int i = 0; i < 2048; i++)asum[i] = 0.;}
void clear(){for(int i = 0; i < 2048; i++)asum[i] = 0.;}
// getsum returns the current sum of the array
double getsum(){double d = 0.; for(int i = 0; i < 2048; i++)d += asum[i];
return(d);}
void addnum(double);
};
void SUM::addnum(double d) // add a number into the array
{
size_t i;
while(1){
// i = exponent of d
i = ((size_t)((*(unsigned long long *)&d)>>52))&0x7ff;
if(i == 0x7ff){ // max exponent, could be overflow
asum[i] += d;
return;
}
if(asum[i] == 0.){ // if empty slot store d
asum[i] = d;
return;
}
d += asum[i]; // else add slot to d, clear slot
asum[i] = 0.; // and continue until empty slot
}
}
使用之类
示例程序:
#include <iostream>
#include <iomanip>
using namespace std;
static SUM sum;
int main()
{
double dsum = 0.;
double d = 1./5.;
unsigned long i;
for(i = 0; i < 0xffffffffUL; i++){
sum.addnum(d);
dsum += d;
}
cout << "dsum = " << setprecision(16) << dsum << endl;
cout << "sum.getsum() = " << setprecision(16) << sum.getsum() << endl;
cout << "0xffffffff * 1/5 = " << setprecision(16) << d * (double)0xffffffffUL << endl;
return(0);
}
(假设所有数字都是正数)对数字进行排序,从最低到最高,然后从最低到最高添加。 (如果出现负数,则按绝对值排序。)并且不需要将每个元素除以N,只需将总和除以N. – 2014-09-28 18:52:33
另请参阅http://stackoverflow.com/q/13417670/(特别是,Kahan求和) – Nemo 2014-09-28 19:01:02