假设您在一个数组中有100000000个32位浮点值,并且每个浮点数的值都介于0.0和1.0之间。如果你试图将它们全部加起来像这样
result = 0.0;
for (i = 0; i < 100000000; i++) {
result += array[i];
}
当result
远大于1.0时,你会遇到问题。
那么有哪些方法可以更准确地执行求和?
答案 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