如何以一种可避免精度错误的方式缓存某个列表中所有浮点值的总和?
我有很多物理形状:m1
,m2
,m3
,...
这些形状连接成一个大体M
= m1
+ m2
+ m3
+ ....
我必须经常请求大体的质量,所以我缓存M
。
现在,我有责任酌情更新M
。
当我添加质量= mi
的形状时: -
M += mi;
当我删除质量为mi
的形状时: -
M -= mi;
程序添加/删除形状一段时间后,
M
远离正确的总和。 (m1
+ m2
+ m3
+ ....)
结果,我的程序最终异常执行
毫无疑问,如果某对mi
和mj
的质量比非常低或非常高,症状会更快显示。
如何专业地缓解这个数字问题?
换句话说: -
我是否应该首先缓存总和M
?
在添加/删除小形状之后,或者(可能)在某些呼叫者请求M
之前,我是否应该每次重新计算总和(以蛮力方式)?
我已阅读https://en.wikipedia.org/wiki/Kahan_summation_algorithm,它只能推迟发布此问题。
答案 0 :(得分:1)
问题是如果指数不同,浮点结果依赖于顺序。例如,如果你这样做
1e0 + 1e20 - 1e20
你会得到
0.0
因为1e0 + 1e20 == 1e20
。但如果你做了
1e20 - 1e20 + 1e0
你会得到
1e0
所以一般来说,你应该总是总结一下群众而不是减去群众。并且应该首先总结最低值,以便它们有可能影响最终结果。如果您首先总结最大值,那么小值将永远不会改变总和。
根据您需要添加的数量,您可以将群体缓存到组中,并且只对受影响的组进行重新求和,然后组合群组的质量。我假设你在这里有很多身体,所以总和可能是昂贵的(即你增加了一百万个身体或类似的东西)。
但如果你只是总结一小部分,那么优化它可能不值得。您应该首先编写代码以使其有效,然后对其进行分析以找到热点。如果你正在进行物理模拟,那么像分区或平方根这样的东西将比添加更加昂贵。
答案 1 :(得分:1)
如果您知道质量范围,可以考虑使用定点算术,并使用int64_t
,它将为您提供19.5位精度,并且只要您永不溢出,求和和减法可以按任何顺序进行,并且始终是精确的。
答案 2 :(得分:0)
根本问题在于您假设浮点类型(float
,double
或您正在使用的任何东西)代表实数。它们不是 - 它们代表离散近似.... a double
通常具有15-17个有效数字的精度,而float
通常具有大约7或8个有效数字的精度。
这意味着您存储的大量值将近似存储(即与您想要的值相比具有相关的错误)。例如,0.1
不能精确地存储在浮点中(因为它不能表示为2
的负幂之和 - 实际上,这通常表示浮点类型中的尾数是如何表示的)
下一个影响是错误传播。任何加法,减法,乘法,除法,取幂等都有具有潜在误差的操作数,并且这些误差在结果中传播 - 可能被放大,可能衰减。处理此问题的“专业”方法是命令操作以减少错误的传播(并预测产生的错误将是什么,而不是假设精确的计算)。
第三个影响是增加或减去大小值会引入错误。因此1.0
+ 1.0e25
会得到1.0e25
的结果。重复添加以获得结果然后减去并重新添加以维持值传播这些类型的错误 - 操作的顺序也很重要。所以1.0 + 1.0e25 - 1.0e25
(假设操作从左到右完成)将得到(大约)零的结果,而1.0e25 - 1.0e25 + 1.0
将给出(大约)1.0
的结果。这可能就是你所看到的(因为物理计算中的质量可能非常大或非常小)。解决方案不是尝试按照您的方式优化结果,而是每次重做添加,或者以某种方式对质量(以及其他相关计算)进行排序。这是一个值得接受性能降低以减少错误计算机会的例子。