在我的c ++应用程序中,我有一个范围(0,1)中的双精度矢量,我必须尽可能准确地计算其总数。 感觉这个问题应该先解决,但我找不到任何东西。
显然迭代矢量上的每个项目并且如果矢量大小很大并且有些项目明显小于其他项目,则执行sum + = vect [i]会累积一个重大错误。
我目前的解决方案就是这个功能:
double sumDoubles(vector<double> arg)// pass by copy
{
sort(arg.rbegin(),arg.rend()); // sort in reverse order
for(int i=1;i<=arg.size();i*=2)
for(int j=0;j<arg.size()-i;j+=(2*i))
arg[j]+=arg[j+i];
return arg[0];
}
基本上它按升序对输入进行排序并计算成对总和:
A + B + C + d + E + F + G + H =((A + B)+(C + d))+((E + F)+(G + H))
就像构建二叉树一样,但要做到位。排序应确保在每个步骤中两个加数具有可比较的大小。
上面的代码确实比具有累积和的单个循环执行得更好。 但是我很好奇是否可以进一步提高精度而不会降低性能太多。
答案 0 :(得分:10)
解决此问题的标准方法之一是Kahan summation algorithm。该算法将您的最坏情况误差降低为依赖于您的浮点精度,而不是与矢量长度成比例增长(并且在O(n)时间内完成,尽管每次迭代计算的次数更多)。
由于您对每次调用进行排序,因此Kahan总和可能会优于您当前的sumDoubles
,并且还会将pairwise summation的错误增长O(log n)提高到O(1)。这就是说,如果sort
是不必要的,那么成对求和可能会胜过Kahan求和(由于所涉及的额外的每次迭代数学),可能(对于你的情况)最小的误差增长。
答案 1 :(得分:2)
你应该按绝对值排序。目前,-100000000.0在0.000000001之前排序。排序的想法是,您可以添加相同大小的术语。如果完全正常则添加-100000000.0和+100000000.0,因此它们应该靠近排序,但添加-100000000.0和0.000000001会导致准确性问题。
您的算法的第二个问题是您的评估顺序非常糟糕。正如您所注意到的那样,您有一个树结构,但即便如此,您也可以按任意顺序评估子表达式。内存访问的最有效顺序是在添加[4]之前添加[0]和[1],然后是[2]和[3],然后是[0]和[2] 。 [5]。原因很简单:你有[0]和[2]仍然在缓存中,它们的值会改变。因此,不要将中间值写回主存储器,以便稍后读取和修改它们。 (在树语言中,这是DFS与BFS评估)
出于相同的缓存效率原因,我还会修改存储临时结果的位置。当然,在[0]中存储[0] + [1]。但在那之后,[1]不再需要了。在[1]中存储[2] + [3]。现在,再次添加[0]和[1]以得到[0]的总和.. [3]存储在[0]中。
总结一下:尽快将中间结果加在一起,减少它们的数量,并将它们存储在数组开头的连续内存中,而不是四处散布。