我正在运行Anaconda的最新版本的Python / Numpy(1.15.4),并与MKL链接。我使用以下epsilon:
epsilon = 2**(-53)
因此1.0 + epsilon等于1.0。然后,定义下面的numpy数组,该数组用epsilon填充,除了前8个等于1的元素。
import numpy as np
n = 1000000
a = np.full(n, epsilon)
a[0:8] = 1.0
如果使用从左到右的经典缩减来计算数组的总和,则应该精确地得到8.0,因为所有epsilon都不会随着缩减的进行而改变。但是我很惊讶地看到
print(np.sum(a))
给您退款8.000000000111008,这不是我期望的。我试图将它们放在数组的中间和结尾,以检查总和是否未向后进行,但没有任何变化。您知道如何计算总金额吗?
PS:我很清楚浮点算法很棘手,而+与浮点不相关,这使得归约结果取决于求和顺序。但是我找不到在这里使用哪种求和顺序。
答案 0 :(得分:1)
如评论中所述,numpy
使用成对求和。因此,当调用堆栈开始解析时,在按对递归求和的末尾,求和将(大致)如下:
(1+1) + (1+1) + (1+1) + (1+1) + (epsilon + epsilon) + ... + (epsilon + epsilon)
(2+2) + (2+2) + (2*epsilon) + (2*epsilon) + ... + (2*epsilon)
(4+4) + (4*epsilon) + (4*epsilon) + ... + (4*epsilon)
8 + (8*epsilon) + (8*epsilon) + ... + (8*epsilon)
8 + (16*epsilon) + (16*epsilon) + ... + (16*epsilon)
...
8 + (999992*epsilon)
您正确地指出1.0 + epsilon
等于1.0
。容易想到x + epsilon == x
对所有x
都是如此。当x
为“大”时,它确实成立,但是当x == epsilon
(即epsilon + epsilon != epsilon
)时,此不成立。因此epsilon + epsilon
项将开始堆积:
In [27]: epsilon = 2**(-53)
In [28]: 1.0 + epsilon == 1.0
Out[28]: True
In [29]: 2.0 + epsilon == 2.0
Out[29]: True
In [30]: epsilon + epsilon == epsilon
Out[30]: False
In [31]: epsilon
Out[31]: 1.1102230246251565e-16
In [32]: epsilon + epsilon
Out[32]: 2.220446049250313e-16
In [33]: 123*epsilon
Out[33]: 1.3655743202889425e-14
我不太能得到numpy
的答案,但是我们可以很接近:
In [36]: 8 + (999992*epsilon)
Out[36]: 8.000000000111022
In [62]: def pairwise_sum(arr):
...: if len(arr) <= 2:
...: return sum(arr)
...: midpoint = len(arr)//2
...: first_half = arr[:midpoint]
...: second_half = arr[midpoint:]
...: return pairwise_sum(first_half) + pairwise_sum(second_half)
...:
In [63]: pairwise_sum(a)
Out[63]: 8.0000000001110205
因此,这里显然有一些 other 区别(一些实现细节),但是希望这可以使您确信numpy
实际上正在使用成对求和。
HTH。