总和精度误差 addition/subtraction(质量)

precision error in summation addition/subtraction (of mass)

如何缓存某个列表中所有浮点值的总和,避免精度错误?

例子

我有很多物理形状:m1m2m3、...
这些形状组合成一个质量为 M = m1+m2+m3+....
的大物体 我要经常请求大体的质量,所以我缓存M

现在,我有责任酌情更新 M

当我添加质量 = mi 的形状时:-

M += mi;

当我移除质量为 mi 的形状时:-

M -= mi;

问题

程序 add/remove 形状一段时间后,
M离正确求和越来越远了。 (m1+m2+m3+....)

结果我的程序终于执行异常了
毫无疑问,如果某对 mimj 的质量比非常低或非常高,则症状会更快出现。

问题

如何专业地缓解这个数值问题?

换句话说:-
我不应该首先缓存总和 M 吗?
我是否应该在小形状 added/removed 之后每次(以蛮力方式)重新计算总和,或者(可能)就在一些呼叫者请求 M 之前?

我已经阅读了https://en.wikipedia.org/wiki/Kahan_summation_algorithm,只能推迟issue

问题是,如果指数不同,浮点结果是顺序相关的。例如,如果您这样做

1e0 + 1e20 - 1e20

你会得到

0.0

因为1e0 + 1e20 == 1e20。但如果你这样做了

1e20 - 1e20 + 1e0

你会得到

1e0

所以总的来说,你应该总是把群众加起来,不要减去。并且应该先求和最低的值,这样他们才有机会影响最终的结果。如果先求和最大的值,那么小的值永远不会改变总和。

根据您需要添加的数量,您可以将群众缓存到组中,并且仅re-sum受影响的组,然后将这些组的群众合并。我假设你这里有很多身体,所以总和可能会很昂贵(即你要添加一百万个身体或类似的东西)。

但如果您只是对一个小数字求和,可能不值得对其进行优化。您应该先编写您的代码,这样才能正常工作,然后对其进行分析以找到热点。如果您正在进行物理模拟,除法或平方根之类的东西将比加法 昂贵。

根本问题是您假设浮点类型(floatdouble 或您使用的任何类型)表示实数。它们不是 - 它们代表离散近似值...... double 通常具有 15-17 位有效数字的精度,而 float 通常具有大约 7 或 8 位有效数字的精度。

这意味着您存储的许多值将被近似存储(即与您想要的值相比存在相关错误)。例如,0.1 不能精确存储在浮点数中(因为它不能表示为 2 的负幂之和 - 实际上,浮点类型中的尾数通常是如何表示的) .

下一个效果是错误传播。任何加法、减法、乘法、除法、求幂等操作数都有潜在的错误,这些错误会在结果中传播——可能被放大,也可能被减弱。 "professional" 处理此问题的方法是对操作进行排序以减少错误的传播(并预测结果错误是什么,而不是假设进行精确计算)。

第三个影响是增加或减少大值和小值会引入错误。所以 1.0 + 1.0e25 将给出 1.0e25 的结果。重复加法以获得结果,然后减法和重新加法以保持值传播这些类型的错误 - 操作顺序再次很重要。所以 1.0 + 1.0e25 - 1.0e25(假设操作是从左到右完成的)将给出(大约)零的结果,而 1.0e25 - 1.0e25 + 1.0 将给出(大约)1.0 的结果。这可能就是您所看到的(因为物理计算中的质量可能非常大或非常小)。解决方案不是尝试按照您的方式优化结果,而是每次都重做加法,或者以某种方式对质量(和其他相关计算)进行排序。这是一个值得接受性能损失以减少错误计算机会的示例。

如果你知道质量的范围,你可以考虑使用定点算法,并使用int64_t可以得到19.5位的精度,只要因为你永远不会溢出,所以可以按任何顺序进行求和和减法,并且始终是精确的。