总和精度误差 addition/subtraction(质量)
precision error in summation addition/subtraction (of mass)
如何缓存某个列表中所有浮点值的总和,避免精度错误?
例子
我有很多物理形状:m1
、m2
、m3
、...
这些形状组合成一个质量为 M
= m1
+m2
+m3
+....
的大物体
我要经常请求大体的质量,所以我缓存M
。
现在,我有责任酌情更新 M
。
当我添加质量 = mi
的形状时:-
M += mi;
当我移除质量为 mi
的形状时:-
M -= mi;
问题
程序 add/remove 形状一段时间后,
M
离正确求和越来越远了。 (m1
+m2
+m3
+....)
结果我的程序终于执行异常了
毫无疑问,如果某对 mi
和 mj
的质量比非常低或非常高,则症状会更快出现。
问题
如何专业地缓解这个数值问题?
换句话说:-
我不应该首先缓存总和 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受影响的组,然后将这些组的群众合并。我假设你这里有很多身体,所以总和可能会很昂贵(即你要添加一百万个身体或类似的东西)。
但如果您只是对一个小数字求和,可能不值得对其进行优化。您应该先编写您的代码,这样才能正常工作,然后对其进行分析以找到热点。如果您正在进行物理模拟,除法或平方根之类的东西将比加法 昂贵。
根本问题是您假设浮点类型(float
、double
或您使用的任何类型)表示实数。它们不是 - 它们代表离散近似值...... 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位的精度,只要因为你永远不会溢出,所以可以按任何顺序进行求和和减法,并且始终是精确的。
如何缓存某个列表中所有浮点值的总和,避免精度错误?
例子
我有很多物理形状:m1
、m2
、m3
、...
这些形状组合成一个质量为 M
= m1
+m2
+m3
+....
的大物体
我要经常请求大体的质量,所以我缓存M
。
现在,我有责任酌情更新 M
。
当我添加质量 = mi
的形状时:-
M += mi;
当我移除质量为 mi
的形状时:-
M -= mi;
问题
程序 add/remove 形状一段时间后,
M
离正确求和越来越远了。 (m1
+m2
+m3
+....)
结果我的程序终于执行异常了
毫无疑问,如果某对 mi
和 mj
的质量比非常低或非常高,则症状会更快出现。
问题
如何专业地缓解这个数值问题?
换句话说:-
我不应该首先缓存总和 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受影响的组,然后将这些组的群众合并。我假设你这里有很多身体,所以总和可能会很昂贵(即你要添加一百万个身体或类似的东西)。
但如果您只是对一个小数字求和,可能不值得对其进行优化。您应该先编写您的代码,这样才能正常工作,然后对其进行分析以找到热点。如果您正在进行物理模拟,除法或平方根之类的东西将比加法 昂贵。
根本问题是您假设浮点类型(float
、double
或您使用的任何类型)表示实数。它们不是 - 它们代表离散近似值...... 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位的精度,只要因为你永远不会溢出,所以可以按任何顺序进行求和和减法,并且始终是精确的。