这是一个允许单步执行十进制增量的自定义函数:
def my_range(start, stop, step):
i = start
while i < stop:
yield i
i += step
它的工作原理如下:
out = list(my_range(0, 1, 0.1))
print(out)
[0, 0.1, 0.2, 0.30000000000000004, 0.4, 0.5, 0.6, 0.7, 0.7999999999999999, 0.8999999999999999, 0.9999999999999999]
现在,这并不奇怪。这是可以理解的,因为浮点不准确并且0.1
在内存中没有确切的表示。因此,这些精度误差是可以理解的。
另一方面拿numpy
:
import numpy as np
out = np.arange(0, 1, 0.1)
print(out)
array([ 0. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9])
有趣的是,这里没有明显的不精确度。我认为这可能与__repr__
显示的内容有关,所以为了确认,我尝试了这个:
x = list(my_range(0, 1.1, 0.1))[-1]
print(x.is_integer())
False
x = list(np.arange(0, 1.1, 0.1))[-1]
print(x.is_integer())
True
所以,我的函数返回一个不正确的上限值(它应该是1.0
但它实际上是1.0999999999999999
),但是np.arange
正确地执行了它。
我知道Is floating point math broken?,但问题的关键是:
答案 0 :(得分:10)
端点的差异是因为NumPy预先计算长度而不是ad hoc,因为它需要预先分配数组。您可以在_calc_length
helper中看到这一点。它不会在它到达结束参数时停止,而是在达到预定长度时停止。
预先计算长度不会使您免于出现非整数步骤的问题,并且您经常会得到“错误”的终点,例如numpy.arange(0.0, 2.1, 0.3)
:
In [46]: numpy.arange(0.0, 2.1, 0.3)
Out[46]: array([ 0. , 0.3, 0.6, 0.9, 1.2, 1.5, 1.8, 2.1])
使用numpy.linspace
会更安全,而不是步长,你会说出你想要多少元素以及是否要包含正确的端点。
在计算元素时,NumPy可能看起来没有出现舍入误差,但这只是由于不同的显示逻辑。 NumPy比float.__repr__
更加积极地截断显示的精度。如果您使用tolist
获取普通Python标量的普通列表(以及普通的float
显示逻辑),您可以看到NumPy也遭遇了舍入错误:
In [47]: numpy.arange(0, 1, 0.1).tolist()
Out[47]:
[0.0,
0.1,
0.2,
0.30000000000000004,
0.4,
0.5,
0.6000000000000001,
0.7000000000000001,
0.8,
0.9]
它遭受了轻微的不同的舍入错误 - 例如,在.6和.7而不是.8和.9 - 因为它还使用了不同的计算元素的方法,在{ {3}}用于相关的dtype。
fill
函数实现的优势在于它使用start + i*step
而不是重复添加步骤,这避免了在每次添加时累积错误。但是,它的缺点是(由于没有令人信服的理由,我可以看到)它从前两个元素重新计算步骤而不是将步骤作为参数,因此它可能在前面的步骤中失去很大的精度。 / p>
答案 1 :(得分:6)
虽然arange
以稍微不同的方式逐步调整范围,但它仍然存在浮动表示问题:
In [1358]: np.arange(0,1,0.1)
Out[1358]: array([ 0. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9])
印刷品隐藏了;将其转换为列表以查看血淋淋的细节:
In [1359]: np.arange(0,1,0.1).tolist()
Out[1359]:
[0.0,
0.1,
0.2,
0.30000000000000004,
0.4,
0.5,
0.6000000000000001,
0.7000000000000001,
0.8,
0.9]
或另一次迭代
In [1360]: [i for i in np.arange(0,1,0.1)] # e.g. list(np.arange(...))
Out[1360]:
[0.0,
0.10000000000000001,
0.20000000000000001,
0.30000000000000004,
0.40000000000000002,
0.5,
0.60000000000000009,
0.70000000000000007,
0.80000000000000004,
0.90000000000000002]
在这种情况下,每个显示的项目都是np.float64
,其中第一项是float
。
答案 2 :(得分:5)
除了列表和数组的不同表示之外,NumPys arange
通过乘法而不是重复添加来工作。它更像是:
def my_range2(start, stop, step):
i = 0
while start+(i*step) < stop:
yield start+(i*step)
i += 1
然后输出完全相同:
>>> np.arange(0, 1, 0.1).tolist() == list(my_range2(0, 1, 0.1))
True
重复添加后,您将“累积”浮点舍入错误。乘法仍然受到舍入的影响,但错误不会累积。
正如评论中指出的那样,并不是真的发生了什么。据我所知,它更像是:
def my_range2(start, stop, step):
length = math.ceil((stop-start)/step)
# The next two lines are mostly so the function really behaves like NumPy does
# Remove them to get better accuracy...
next = start + step
step = next - start
for i in range(length):
yield start+(i*step)
但不确定这是否完全正确,因为NumPy还有很多事情发生。