我的Java教科书指出,在处理浮点数时,从最大到最小的添加不如从最小到最大添加准确。但是,他并没有明确解释为什么会这样。
答案 0 :(得分:2)
浮点数的精度位数有限(float
为6,double
为15)。计算
1.0e20d + 1
给出结果1.0e20
,因为没有足够的精度来表示数字
100,000,000,000,000,000,001
如果您从最大数字开始,那么任何超过n
个数量级的数字(n
为6或15,取决于类型)将不会对总和做出贡献。从最小的开始,您可以将几个较小的数字合并为一个会影响最终总数的数字。
它会产生影响的地方,例如
1.0e20 + 1.0e4 + 6.0e4 + 3.0e4
假设它精确到15位十进制数字(不是,请参阅下面的链接文章,但15对于该示例来说已经足够了),如果从较大的数字开始,其他任何一个都不会有所作为,因为它们是太小。如果从较小的那些开始,它们总计为1.0e5,这足以影响最终总数。
请阅读What Every Computer Scientist Should Know About Floating-Point Arithmetic
答案 1 :(得分:0)
Nick Higham在section 4.2 of "Accuracy and Stability of Numerical Algorithms"中提供了一个很好的解释。以下是我对此的随意解释:
浮点的关键属性是,当单个操作的结果无法准确表示时,它会四舍五入到最接近的值。这有很多后果,即加法(和乘法)不再是associative。
需要注意的另一个主要问题是错误(真值和舍入值之间的差异)是相对的。如果我们使用方括号([]
)来表示此舍入操作,那么我们将拥有任何数字x
的属性:
|[x] - x| <= ϵ |[x]| / 2
其中ε是machine epsilon。
假设我们要总结[x1, x2, x3, x4]
。显而易见的方法是通过
s2 = x1 + x2
s3 = s2 + x3 = x1 + x2 + x3
s4 = s3 + x4 = x1 + x2 + x3 + x4
如上所述,我们不能完全这样做,所以我们实际上在做:
t2 = [x1 + x2]
t3 = [t2 + x3] = [[x1 + x2] + x3]
t4 = [t3 + x4] = [[[x1 + x2] + x3] +x4]
结果错误|t4 - s4|
有多大?我们知道
|t2 - s2| = |[x1+x2] - (x1+x2)| <= ϵ/2 |t2|
现在我们可以写Triangle inequality
|t3 - s3| = |[t2+x3] - (t2+x3) + (t2+x3) - (s2+x3)|
<= |[t2+x3] - (t2+x3)| + |t2 - s2|
<= ϵ/2 (|t3| + |t2|)
再次:
|t4 - s4| = |[t3+x4] - (t3+x4) + (t3+x4) - (s3+x4)|
<= |[t3+x4] - (t3+x4)| + |t3 - s3|
<= ϵ/2 (|t4| + |t3| + |t2|)
这导致了Higham的一般建议:
在设计或选择求和方法以实现高精度时,目标应该是最小化中间和
ti
的绝对值。
因此,如果您正在进行顺序求和(就像我们上面所做的那样),那么您希望从最小的元素开始,因为这将为您提供最小的中间和。
但这不是唯一的选择!还有pairwise summation,您可以在树形式中添加对(例如[[x1 + x2] + [x3 + x4]]
),但这需要分配工作数组。您还可以通过将中间和存储在向量中来利用SIMD vectorisation,这可以提高速度和精度。