计算总和时使用比所需精度更高的精度
Use higher precision than needed when computing a sum
在计算总和时使用较大的精度并在算法结束时降低精度是否是一种好的做法?喜欢
float average(const float* begin, const float* end)
{
double sum=0;
size_t N=end-begin;
while(begin!=end)
{
sum+=(double)(*begin);
++begin;
}
return (float)( sum/N); //Assume range is not empty
}
可能是吧,因为积累的时候误差比较少。另一方面,在数据类型之间进行转换时可能会出错。
不是好的一件事是在最后降低精度。
无论如何,您的代码除以零,因为当您进行除法时,begin == end。
我不会:做这种事情进一步将您的实施与特定平台联系起来。在 C 中无法保证 float
不如 double
精确,最后的精度降低不是好的做法,从计算上讲也不是特别便宜。
我会让编译器完成它的工作。
当以浮点形式添加数字时,最好先累加较小的数量级数字。这样他们就有更好的机会为总和做出贡献。有更高级的浮点求和方法;你也应该考虑一下。
这取决于您要避免什么,但可能不是。
如果您试图避免灾难性取消(其中 10^100 + 1 - 10^100
结果为 0 而不是 1),使用更广泛的 FP 类型会有一点帮助,但不是很大。
如果数字在数量级上靠得更近,但您仍然担心随着总和的增长(例如 1e-8 + 1e-8 + (1e8 copies) != 1
),LSB 会从末尾掉下来,那么较宽的类型 可以 帮助,但同样,只是在一定程度上。
真正 有用的是更聪明的浮点求和方法。最简单的方法称为 "pairwise summation",您可以将数字数组视为二叉树的叶子,并递归地对它们对求和,直到只剩下一个数字。对于在那里进行的迭代求和,您还可以先对数字进行排序,这往往会减少错误。还有更复杂、更精确的方法可用... google "compensated summation" 了解详情。
综上所述,如果您怀疑舍入误差会成为您的问题,double sum
会有所帮助,但可能还不够。
哦,关于 "things can go wrong when converting between the data types":事情 可能 出错(特别是双舍入错误),但您可能会看到不精确与执行求和本身的误差相比,它们的误差并不显着。
Sneftel 提到了求和的方法。下面是一组函数,可处理 2048 个 IEEE 64 位双精度数组(由调用者传递)。 (假设unsigned long long也是64位).
/* clear array */
void clearsum(double asum[2048])
{
size_t i;
for(i = 0; i < 2048; i++)
asum[i] = 0.;
}
/* add a number into array */
void addtosum(double d, double asum[2048])
{
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 */
}
}
/* return sum from array */
double returnsum(double asum[2048])
{
double sum = 0.;
size_t i;
for(i = 0; i < 2048; i++)
sum += asum[i];
return sum;
}
在计算总和时使用较大的精度并在算法结束时降低精度是否是一种好的做法?喜欢
float average(const float* begin, const float* end)
{
double sum=0;
size_t N=end-begin;
while(begin!=end)
{
sum+=(double)(*begin);
++begin;
}
return (float)( sum/N); //Assume range is not empty
}
可能是吧,因为积累的时候误差比较少。另一方面,在数据类型之间进行转换时可能会出错。
不是好的一件事是在最后降低精度。
无论如何,您的代码除以零,因为当您进行除法时,begin == end。
我不会:做这种事情进一步将您的实施与特定平台联系起来。在 C 中无法保证 float
不如 double
精确,最后的精度降低不是好的做法,从计算上讲也不是特别便宜。
我会让编译器完成它的工作。
当以浮点形式添加数字时,最好先累加较小的数量级数字。这样他们就有更好的机会为总和做出贡献。有更高级的浮点求和方法;你也应该考虑一下。
这取决于您要避免什么,但可能不是。
如果您试图避免灾难性取消(其中 10^100 + 1 - 10^100
结果为 0 而不是 1),使用更广泛的 FP 类型会有一点帮助,但不是很大。
如果数字在数量级上靠得更近,但您仍然担心随着总和的增长(例如 1e-8 + 1e-8 + (1e8 copies) != 1
),LSB 会从末尾掉下来,那么较宽的类型 可以 帮助,但同样,只是在一定程度上。
真正 有用的是更聪明的浮点求和方法。最简单的方法称为 "pairwise summation",您可以将数字数组视为二叉树的叶子,并递归地对它们对求和,直到只剩下一个数字。对于在那里进行的迭代求和,您还可以先对数字进行排序,这往往会减少错误。还有更复杂、更精确的方法可用... google "compensated summation" 了解详情。
综上所述,如果您怀疑舍入误差会成为您的问题,double sum
会有所帮助,但可能还不够。
哦,关于 "things can go wrong when converting between the data types":事情 可能 出错(特别是双舍入错误),但您可能会看到不精确与执行求和本身的误差相比,它们的误差并不显着。
Sneftel 提到了求和的方法。下面是一组函数,可处理 2048 个 IEEE 64 位双精度数组(由调用者传递)。 (假设unsigned long long也是64位).
/* clear array */
void clearsum(double asum[2048])
{
size_t i;
for(i = 0; i < 2048; i++)
asum[i] = 0.;
}
/* add a number into array */
void addtosum(double d, double asum[2048])
{
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 */
}
}
/* return sum from array */
double returnsum(double asum[2048])
{
double sum = 0.;
size_t i;
for(i = 0; i < 2048; i++)
sum += asum[i];
return sum;
}