私の推測では、バイナリ分解はカハンの合計とほぼ同じように機能すると思います。
これを説明する例を次に示します。
#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 と宣言し、x86 アーキテクチャで余分な精度を避けるために -float-store でコンパイルしました
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
これには少し説明が必要です。
- 最初に単純な合計を表示します
- 次に、倍精度の合計(カハンはそれとほぼ同等です)
- 最初の近似は、バイナリ分解と同じです。合計を data[0] に保存し、剰余を保存することを除いて。このようにして、合計の前後のデータの正確な合計は変更されません
- これにより、1 回目の反復を修正するために 2 回目の反復で剰余を合計することで誤差を概算できます (バイナリ和に Kahan を適用するのと同じです)。
- さらに反復することで、結果をさらに絞り込むことができ、収束が見られます