与顺序无关的浮点求和[重复]
Posted
技术标签:
【中文标题】与顺序无关的浮点求和[重复]【英文标题】:Order-independent floating point summation [duplicate] 【发布时间】:2020-04-08 16:48:39 【问题描述】:我知道浮点加法不是关联的:(a + b) + c
通常不等于a + (b + c)
。所以这个求和算法可以根据输入的顺序给出不同的结果:
float naive_sum(float[] input)
float accumulator = 0;
for (float x : input)
accumulator += x;
return accumulator;
是否可以使这个顺序独立,以便即使输入被打乱也返回相同的结果?我不是想减少舍入误差:我只是希望它与订单无关。
一个想法是先对输入进行排序:
float sort_sum(float[] input)
return naive_sum(sort(input));
sort
不必将浮点数按数字顺序排列;它只需要满足sort(input) == sort(shuffle(input))
。我认为这可行,但它不再像 naive_sum
那样是恒定的空间和线性时间。
另一个想法是让累加器成为一个巨大的整数类型:大到足以容纳任何浮点数而无需四舍五入。如果浮点数具有 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);
现在又是常数空间和线性时间,但是有这么大的累加器,可能是一个非常慢的线性时间。 :)
对于浮点数的顺序无关求和是否有任何实用算法?
【问题讨论】:
是否可以接受特定于 Java 的解决方案? 累加器方法可能没有你想象的那么糟糕:获取指数,用它索引累加器,加法,然后进位。进位偶尔会传播很多,但很少传播,除非应用程序设计中的某些东西导致它。然而,除了受累加器宽度限制之外,它并不是真正的恒定时间。也就是说,发生多少次进位会影响时间。但它通常很小。 【参考方案1】:如果您的语言具有高精度的十进制类型,例如 Java 的 java.math.BigDecimal
,请使用它来进行求和。从float
或double
到BigDecimal
的转换是精确的。如果您未指定需要舍入 BigDecimal
的 MathContext
,则添加也是精确的。最终的BigDecimal
值将是输入的实数和,实数加法是关联和交换的。唯一的舍入和舍入错误将在转换回float
时出现,无论输入顺序如何,都会转换相同的数字。
这类似于您的累加器想法,但利用了已经测试过的数据类型和限制“累加器”大小的内存管理。
private static float sum(float[] data)
BigDecimal adder = new BigDecimal(0);
for(float f : data)
adder = adder.add(new BigDecimal(f));
return adder.floatValue();
【讨论】:
【参考方案2】:“(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... or 3,2,1... or 1,20,2,19,3,18... or...) 然后计算 所有总和的平均值。
最常用的(我相信)技术是扩大使用的位数。例如 64 位或 128 位而不是 32 位。或任意精度算术。价格为 128 位或更高的精度使计算速度变慢。
存在“鲁棒谓词”和this EGC site,它们试图将错误减少到最低限度,低于浮点/双小数。
【讨论】:
以上是关于与顺序无关的浮点求和[重复]的主要内容,如果未能解决你的问题,请参考以下文章
与 sys.path 顺序无关的与 SDK 包导入同名的 python 站点包