我的猜测是,您的二进制分解几乎与 Kahan 求和一样有效。
这是一个例子来说明它:
#include <stdio.h>
#include <stdlib.h>
#include <algorithm>
void sumpair( float *a, float *b)
{
volatile float sum = *a + *b;
volatile float small = sum - std::max(*a,*b);
volatile float residue = std::min(*a,*b) - small;
*a = sum;
*b = residue;
}
void sumpairs( float *a,size_t size, size_t stride)
{
if (size <= stride*2 ) {
if( stride<size )
sumpair(a+i,a+i+stride);
} else {
size_t half = 1;
while(half*2 < size) half*=2;;
sumpairs( a , half , stride );
sumpairs( a+half , size-half , stride );
}
}
void sumpairwise( float *a,size_t size )
{
for(size_t stride=1;stride<size;stride*=2)
sumpairs(a,size,stride);
}
int main()
{
float data[10000000];
size_t size= sizeof data/sizeof data[0];
for(size_t i=0;i<size;i++) data[i]=((1<<30)*-1.0+random())/(1.0+random());
float naive=0;
for(size_t i=0;i<size;i++) naive+=data[i];
printf("naive sum=%.8g\n",naive);
double dprec=0;
for(size_t i=0;i<size;i++) dprec+=data[i];
printf("dble prec sum=%.8g\n",(float)dprec);
sumpairwise( data , size );
printf("1st approx sum=%.8g\n",data[0]);
sumpairwise( data+1 , size-1);
sumpairwise( data , 2 );
printf("2nd approx sum=%.8g\n",data[0]);
sumpairwise( data+2 , size-2);
sumpairwise( data+1 , 2 );
sumpairwise( data , 2 );
printf("3rd approx sum=%.8g\n",data[0]);
return 0;
}
我声明了我的操作数 volatile 并使用 -ffloat-store 进行编译以避免 x86 架构上的额外精度
g++ -ffloat-store -Wl,-stack_size,0x20000000 test_sum.c
并得到:(0.03125 是 1ULP)
naive sum=-373226.25
dble prec sum=-373223.03
1st approx sum=-373223
2nd approx sum=-373223.06
3rd approx sum=-373223.06
这值得一点解释。
- 我首先显示幼稚求和
- 然后是双精度求和(Kahan 大致相当于那个)
- 第一个近似值与您的二进制分解相同。除了我将总和存储在 data[0] 中并且我关心存储余数。这样求和前后数据的准确总和不变
- 这使我能够通过对第 2 次迭代的残差求和来近似误差,以纠正第 1 次迭代(相当于将 Kahan 应用于二进制求和)
- 通过进一步迭代,我可以进一步细化结果,我们看到了收敛