python – 比较NumPy arange和自定义范围函数,用于生成具有十进制增量的范围

这是一个自定义函数,允许单步执行十进制增量:

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?,但这个问题的重点是:

numpy怎么做到这一点?

最佳答案
端点的差异是因为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标量的普通列表(以及普通的浮点显示逻辑),你会发现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 – 因为它还使用了不同的计算元素的方法,在fill function中为相关的dtype实现.

填充函数实现的优点是它使用start i * step而不是重复添加步骤,这避免了在每次添加时累积错误.然而,它的缺点是(由于没有令人信服的理由,我可以看到)它从前两个元素重新计算步骤而不是将步骤作为参数,因此它可能在前面的步骤中失去很大的精度.

转载注明原文:python – 比较NumPy arange和自定义范围函数,用于生成具有十进制增量的范围 - 代码日志