为什么标准库中没有浮点范围实现?
正如此处所有帖子所明确指出的,range() 没有浮点版本。也就是说,如果我们考虑到range() 函数经常用作索引(当然,这意味着访问器)生成器,那么省略是有道理的。因此,当我们调用range(0,40) 时,实际上是在说我们想要从 0 开始到 40 的 40 个值,但不包括 40 本身。
当我们考虑到索引的生成与索引的数量和它们的值一样多时,在标准库中使用 range() 的浮点实现就没有意义了。例如,如果我们调用函数 frange(0, 10, 0.25),我们会期望 0 和 10 都包含在内,但这会产生一个有 41 个值的生成器,而不是 10/0.25 可能期望的 40 个值。
因此,根据其用途,frange() 函数将始终表现出反直觉的行为;从索引的角度来看,它要么具有太多的值,要么不包括从数学角度合理应该返回的数字。换句话说,很容易看出这样一个函数如何将两个非常不同的用例混为一谈——命名意味着索引用例;这种行为暗示了一种数学行为。
数学用例
话虽如此,正如其他帖子中所讨论的,numpy.linspace() 从数学角度很好地执行了生成:
numpy.linspace(0, 10, 41)
array([ 0. , 0.25, 0.5 , 0.75, 1. , 1.25, 1.5 , 1.75,
2. , 2.25, 2.5 , 2.75, 3. , 3.25, 3.5 , 3.75,
4. , 4.25, 4.5 , 4.75, 5. , 5.25, 5.5 , 5.75,
6. , 6.25, 6.5 , 6.75, 7. , 7.25, 7.5 , 7.75,
8. , 8.25, 8.5 , 8.75, 9. , 9.25, 9.5 , 9.75, 10.
])
索引用例
从索引的角度来看,我编写了一种稍微不同的方法,其中包含一些技巧性的字符串魔术,允许我们指定小数位数。
# Float range function - string formatting method
def frange_S (start, stop, skip = 1.0, decimals = 2):
for i in range(int(start / skip), int(stop / skip)):
yield float(("%0." + str(decimals) + "f") % (i * skip))
同样,我们也可以使用内置的round函数并指定小数位数:
# Float range function - rounding method
def frange_R (start, stop, skip = 1.0, decimals = 2):
for i in range(int(start / skip), int(stop / skip)):
yield round(i * skip, ndigits = decimals)
快速比较和性能
当然,鉴于上述讨论,这些功能的用例相当有限。尽管如此,这里有一个快速比较:
def compare_methods (start, stop, skip):
string_test = frange_S(start, stop, skip)
round_test = frange_R(start, stop, skip)
for s, r in zip(string_test, round_test):
print(s, r)
compare_methods(-2, 10, 1/3)
每个结果都是相同的:
-2.0 -2.0
-1.67 -1.67
-1.33 -1.33
-1.0 -1.0
-0.67 -0.67
-0.33 -0.33
0.0 0.0
...
8.0 8.0
8.33 8.33
8.67 8.67
9.0 9.0
9.33 9.33
9.67 9.67
还有一些时间安排:
>>> import timeit
>>> setup = """
... def frange_s (start, stop, skip = 1.0, decimals = 2):
... for i in range(int(start / skip), int(stop / skip)):
... yield float(("%0." + str(decimals) + "f") % (i * skip))
... def frange_r (start, stop, skip = 1.0, decimals = 2):
... for i in range(int(start / skip), int(stop / skip)):
... yield round(i * skip, ndigits = decimals)
... start, stop, skip = -1, 8, 1/3
... """
>>> min(timeit.Timer('string_test = frange_s(start, stop, skip); [x for x in string_test]', setup=setup).repeat(30, 1000))
0.024284090992296115
>>> min(timeit.Timer('round_test = frange_r(start, stop, skip); [x for x in round_test]', setup=setup).repeat(30, 1000))
0.025324633985292166
看起来字符串格式化方法在我的系统上占了上风。
限制
最后,对上述讨论的观点和最后一个限制进行演示:
# "Missing" the last value (10.0)
for x in frange_R(0, 10, 0.25):
print(x)
0.25
0.5
0.75
1.0
...
9.0
9.25
9.5
9.75
此外,当skip 参数不能被stop 值整除时,考虑到后一个问题,可能会出现打哈欠的差距:
# Clearly we know that 10 - 9.43 is equal to 0.57
for x in frange_R(0, 10, 3/7):
print(x)
0.0
0.43
0.86
1.29
...
8.14
8.57
9.0
9.43
有一些方法可以解决这个问题,但归根结底,最好的方法可能是只使用 Numpy。