与顺序无关的浮点求和

Order-independent floating point summation

我知道浮点加法不是关联的:(a + b) + c 通常不等于 a + (b + c)。所以这个求和的算法可以根据输入的顺序给出不同的结果:

float naive_sum(float[] input) {
  float accumulator = 0;
  for (float x : input) {
    accumulator += x;
  }
  return accumulator;
}

是否可以使此顺序无关,以便 returns 即使输入被打乱也能得到相同的结果?我不是要减少舍入误差:我只是希望它与顺序无关。

一个想法是先对输入进行排序:

float sort_sum(float[] input) {
  return naive_sum(sort(input));
}

sort 不必按数字顺序排列浮点数;它只需要满足 sort(input) == sort(shuffle(input))。我认为这可行,但它不再像 naive_sum 那样恒定 space 和线性时间。

另一个想法是使累加器成为一个巨大的整数类型:大到足以容纳任何浮点数而无需四舍五入。如果浮点数有 11 位指数,则需要大约 2^11 位,即大约 2000 位。

float fixedpoint_sum(float[] input) {
  int2048 accumulator = 0;
  for (float x : input) {
    accumulator += float_to_fixed(x);
  }
  return fixed_to_float(accumulator);
}

现在又是常量 space 和线性时间了,但是有这么大的累加器,也许它是一个非常慢的线性时间。 :)

有没有实用的浮点数求和顺序无关的算法?

(a+b)+c 不等于 a+(b+c)”的问题是因为计算机不能无限精确地工作,它们在数学上并不精确;但他们使用某种丢失数字的表示形式。

阅读 What Every Computer Scientist Should Know About Floating-Point Arithmetic 了解详细说明。

这种表示具有粒度,这意味着两个连续表示之间的差异不是恒定的。大数不能与小数相加:1.1E20 + 1E-5 = 1.1E20

一些小改进:

  • 为了减少这个大大小小的问题,您可以对数字进行排序。所以 小值的总和可能达到大的足够大 值和加法可能更准确。还是没有 保证好结果。

  • 另一种技术可能是以不同的顺序多次求和 (1,2,3... 或 3,2,1... 或 1,20,2,19,3,18... 或...) 然后计算 所有总和的平均值。

最常用的(我相信)技术是扩大使用的位数。例如 64 位或 128 位而不是 32 位。或任意精度算术。价格是128位或更高的精度使计算速度变慢。

存在 "Robust Predicates" 和 this EGC site,它们试图将误差降至最低,低于 float/double epsilon。

如果您的语言具有高精度小数类型,例如 Java 的 java.math.BigDecimal,请使用它来进行求和。从 floatdoubleBigDecimal 的转换是准确的。如果不指定需要舍入的 MathContext BigDecimal 加法也是精确的。最终的 BigDecimal 值将是输入的实数和,实数加法是结合和交换的。唯一的舍入和舍入错误是在转换回 float 时,无论输入顺序如何,都会转换相同的数字。

这类似于您的累加器想法,但利用了已经测试过的数据类型和内存管理来限制 "accumulator" 的大小。

private static float sum(float[] data) {
    BigDecimal adder = new BigDecimal(0);
    for(float f : data) {
        adder = adder.add(new BigDecimal(f));
    }
    return adder.floatValue();
}