将大量小浮点数添加到一起的好方法是什么?

时间:2010-03-16 16:49:26

标签: algorithm floating-point numerical

假设您在一个数组中有100000000个32位浮点值,并且每个浮点数的值都介于0.0和1.0之间。如果你试图将它们全部加起来像这样

result = 0.0;
for (i = 0; i < 100000000; i++) {
    result += array[i];
}

result远大于1.0时,你会遇到问题。

那么有哪些方法可以更准确地执行求和?

5 个答案:

答案 0 :(得分:29)

听起来您想使用Kahan Summation

根据维基百科,

  

Kahan求和算法(也称为补偿求和)显着降低了通过添加有限精度浮点数序列获得的总数中的数值误差,与显而易见的方法。这是通过保持单独的运行补偿(一个变量来累积小错误)来完成的。

     

在伪代码中,算法是:

function kahanSum(input)
 var sum = input[1]
 var c = 0.0          //A running compensation for lost low-order bits.
 for i = 2 to input.length
  y = input[i] - c    //So far, so good: c is zero.
  t = sum + y         //Alas, sum is big, y small, so low-order digits of y are lost.
  c = (t - sum) - y   //(t - sum) recovers the high-order part of y; subtracting y recovers -(low part of y)
  sum = t             //Algebraically, c should always be zero. Beware eagerly optimising compilers!
 next i               //Next time around, the lost low part will be added to y in a fresh attempt.
return sum

答案 1 :(得分:1)

假设C或C ++,将结果设为double。

答案 2 :(得分:1)

如果你能容忍一些额外的空间(用Java):

float temp = new float[1000000];
float temp2 = new float[1000];
float sum = 0.0f;
for (i=0 ; i<1000000000 ; i++) temp[i/1000] += array[i];
for (i=0 ; i<1000000 ; i++) temp2[i/1000] += temp[i];
for (i=0 ; i<1000 ; i++) sum += temp2[i];

基本上是标准的分治算法。这只适用于数字随机分散的情况;如果前五亿的数字是1e-12而后二十亿的数字要大得多,它将无法运作。

但在做任何这样的事情之前,人们可能只是将结果累积在一个双精度中。这会有很大的帮助。

答案 3 :(得分:0)

如果在.NET中使用IEnumerable上存在的LINQ .Sum()扩展方法。那就是:

var result = array.Sum();

答案 4 :(得分:0)

绝对最佳的方法是使用优先级队列,方法如下:

PriorityQueue<Float> q = new PriorityQueue<Float>();
for(float x : list) q.add(x);
while(q.size() > 1) q.add(q.pop() + q.pop());
return q.pop();

(此代码假设数字为正数;通常队列应按绝对值排序)

说明:给出一个数字列表,为了尽可能精确地添加它们,你应该努力使数字接近,t.i。消除小型和大型之间的差异。这就是为什么你想要加上两个最小的数字,从而增加列表的最小值,减少列表中最小值和最大值之间的差异,并将问题大小减少1。

不幸的是,考虑到你正在使用OpenCL,我不知道如何对其进行矢量化。但我几乎可以肯定它可以。你可以看一下关于矢量算法的书,令人惊讶的是它们实际上有多强大:Vector Models for Data-Parallel Computing