【问题标题】:What's a good way to add a large number of small floats together?将大量小浮动添加在一起的好方法是什么?
【发布时间】:2011-01-28 05:15:32
【问题描述】:

假设您在一个数组中有 100000000 个 32 位浮点值,每个浮点数的值都介于 0.0 和 1.0 之间。如果你试着像这样总结它们

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

result 比 1.0 大得多时会遇到问题。

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

【问题讨论】:

  • 为什么期望结果小于 1?我很困惑!
  • 我认为他是在说一旦结果通过了 1.0,问题就开始出现了。 什么问题我不知道,但我就是这样处理的。
  • 在 Python 中,使用 math.fsum (docs.python.org/library/math.html#math.fsum)。
  • 我认为从示例代码中我们可以假设它不是 Python。
  • @splicer:你能说得更具体点吗?你的“问题”是什么意思?

标签: algorithm floating-point numerical


【解决方案1】:

假设 C 或 C++,使结果为双精度。

【讨论】:

  • 是的,这会有所帮助,但是如果要求和的值远远超过 100000000 个呢?对于这个问题,我选择 100000000 是任意的。
【解决方案2】:

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

var result = array.Sum();

【讨论】:

  • 谢谢,但我应该更具体一点:我正在使用 C 和 OpenCL。
  • 这也没有真正解决错误累积问题。
【解决方案3】:

听起来你想使用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

【讨论】:

  • 正是我想要的!谢谢:)
  • 我被告知您必须小心编译器优化,这可能会执行操作的重新排列并假设在下溢情况下不正确的关联规则。您可能需要查看中间代码或程序集来验证。编译器可能中断的代码行是:“t = sum + y”和“c = (t - sum) - y”。使用无限精度算术,(t - sum) 将完全等于 y,而 c 将始终为零。
  • @PaulChernoch:是的,某些编译器优化可能会破坏这一点(其中一个 cmets 甚至指出:“小心急切地优化编译器!”)。在 gcc 上,除非您使用 --ffast-math,否则应该没问题。 (此标志故意破坏了 IEEE-754 提供的一些保证,因此除非您明确要求,否则 AFAIK 永远不会打开它)。 AFAIK,默认情况下没有编译器执行假设无限精度算术的优化,正是因为这些类型的操作会被破坏。
【解决方案4】:

如果你能容忍一点额外的空间(在 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];

基本上是标准的分治算法。这仅在数字随机分散时才有效;如果前 5 亿个数字是 1e-12 而后 5 个亿更大,那么它就行不通了。

但在执行任何操作之前,可能只是将结果累积为双精度数。这会很有帮助。

【讨论】:

    【解决方案5】:

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

    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

    【讨论】:

    • 其实这不是最佳解决方案。您希望最小化中间结果的绝对值,这并不一定意味着您应该始终首先添加最小的数字。例如,如果要对[1.01, -0.001, -1.02, 0.0012]求和,最好表示为(0.0012 - 0.001) + (1.01 - 1.02)。
    猜你喜欢
    • 2011-08-14
    • 1970-01-01
    • 1970-01-01
    • 2018-08-25
    • 2014-04-06
    • 2011-12-16
    • 1970-01-01
    • 2023-04-04
    • 1970-01-01
    相关资源
    最近更新 更多