我知道a similar question,但我想请求人们对我的算法的意见,以尽可能准确地将浮点数与实际成本相加。
这是我的第一个解决方案:
put all numbers into a min-absolute-heap. // EDIT as told by comments below
pop the 2 smallest ones.
add them.
put the result back into the heap.
continue until there is only 1 number in the heap.
这个将采用O(n * logn)而不是正常的O(n)。这真的值得吗?
第二种解决方案来自我正在研究的数据的特征。 这是一个巨大的正数字列表,类似的数量级。
a[size]; // contains numbers, start at index 0
for(step = 1; step < size; step<<=1)
for(i = step-1; i+step<size; i+=2*step)
a[i+step] += a[i];
if(i < size-1)
a[size-1] += a[i];
基本思想是在二叉树中进行求和&#39;方式。
注意:它是伪C代码。 step<<=1
表示逐步乘以2。
这个需要O(n)。
我觉得可能有更好的方法。你能推荐/批评吗?
答案 0 :(得分:20)
Kahan's summation algorithm明显比直接求和更精确,它在O(n)中运行(比直接求和慢1-4倍,具体取决于浮点与数据访问的比较速度。绝对小于桌面硬件上的速度慢4倍,没有任何数据混乱)。
或者,如果您使用的是通常的x86硬件,并且您的编译器允许访问80位long double
类型,只需使用简单的求和算法和long double
类型的累加器。只在最后将结果转换为double
。
如果您确实需要很多精确度,可以使用long double
对变量c
,y
,t
,{{1}合并以上两种解决方案在Kahan的求和算法中。
答案 1 :(得分:9)
如果您担心减少总和中的数字误差,那么您可能会对Kahan's algorithm感兴趣。
答案 2 :(得分:2)
我的猜测是你的二进制分解几乎和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
这值得一点解释。
答案 3 :(得分:1)
元素将按递增顺序放入堆中,因此您可以使用两个队列。如果数字是预先排序的,则产生O(n)。
如果输入已预先排序且排序算法检测到:
,则此伪代码会产生与算法相同的结果,并在O(n)
中运行
Queue<float> leaves = sort(arguments[0]).toQueue();
Queue<float> nodes = new Queue();
popAny = #(){
if(leaves.length == 0) return nodes.pop();
else if(nodes.length == 0) return leaves.pop();
else if(leaves.top() > nodes.top()) return nodes.pop();
else return leaves.pop();
}
while(leaves.length>0 || nodes.length>1) nodes.push(popAny()+popAny());
return nodes.pop();