在Python中是否有适用于浮点数的range()
等效函数?
>>> range(0.5,5,1.5)
[0, 1, 2, 3, 4]
>>> range(0.5,5,0.5)
Traceback (most recent call last):
File "<pyshell#10>", line 1, in <module>
range(0.5,5,0.5)
ValueError: range() step argument must not be zero
您可以使用以下方法之一:
[x / 10.0 for x in range(5, 50, 15)]
或者使用 lambda / map:
map(lambda x: x/10.0, range(5, 50, 15))
arange(0.5, 5, 1.5)
更易读。 - Grzegorz Rożnieckilist(frange(0, 1, 0.5))
,它可以正常工作并且1被排除在外,但是如果您尝试 list(frange(0, 1, 0.1))
,则您得到的最后一个值接近于1.0,这可能不是您想要的结果。这里提供的解决方案没有这个问题。 - blubberdiblubdef frange(x, y, jump):
while x < y:
yield x
x += jump
正如评论所提到的,这可能会产生不可预测的结果,例如:
>>> list(frange(0, 100, 0.1))[-1]
99.9999999999986
为了得到预期的结果,您可以使用本问题中的其他答案之一,或者如@Tadhg所提到的,您可以将decimal.Decimal
作为jump
参数使用。请确保用字符串而不是浮点数进行初始化。>>> import decimal
>>> list(frange(0, 100, decimal.Decimal('0.1')))[-1]
Decimal('99.9')
甚至可以这样:
import decimal
def drange(x, y, jump):
while x < y:
yield float(x)
x += decimal.Decimal(jump)
然后:
>>> list(drange(0, 100, '0.1'))[-1]
99.9
[编辑说明:如果您只使用正的jump
和整数x
和y
开始和结束,那么这个方法可以正常工作。如需更通用的解决方案,请参见此处。]
>>> print list(frange(0,100,0.1))[-1]==100.0
will be False
- Volodimir Kopeyfrange
可能会出现意外情况。由于浮点数算术的诅咒,例如 frange(0.0, 1.0, 0.1)
会产生11个值,其中最后一个值是 0.9999999999999999
。一个实用的改进方法是 while x + sys.float_info.epsilon < y:
,尽管即使这种方法在处理大数时也可能失败。 - Akseli Paléndecimal.Decimal
作为步长,而不是浮点数,那么这个方法会非常有效。 - Tadhg McDonald-Jensen我曾经使用numpy.arange
,但由于浮点运算误差,控制其返回元素数量时出现了一些问题。因此现在我使用linspace
,例如:
>>> import numpy
>>> numpy.linspace(0, 10, num=4)
array([ 0. , 3.33333333, 6.66666667, 10. ])
decimal
,仍然存在浮点误差,例如:np.linspace(-.1,10,num=5050)[0]
。 - TNTnp.linspace(-.1,10,num=5050)[0] == -.1
是真的。只是 repr(np.float64('-0.1'))
显示了更多的数字。 - wim1.0
时,print(numpy.linspace(0, 3, 148)[49])
输出了 0.9999999999999999
。相比于 arange
,linspace
做得更好,但不能保证产生最小可能的舍入误差。 - user2357112Pylab有frange
(实际上是matplotlib.mlab.frange
的包装器):
>>> import pylab as pl
>>> pl.frange(0.5,5,0.5)
array([ 0.5, 1. , 1.5, 2. , 2.5, 3. , 3.5, 4. , 4.5, 5. ])
渴望地评估(2.x range
):
[x * .5 for x in range(10)]
延迟计算(2.x 中的 xrange
,3.x 中的 range
):
itertools.imap(lambda x: x * .5, xrange(10)) # or range(10) as appropriate
或者:
itertools.islice(itertools.imap(lambda x: x * .5, itertools.count()), 10)
# without applying the `islice`, we get an infinite stream of half-integers.
(x * .5 for x in range(10))
作为生成器表达式进行惰性求值呢?+1 - Tim Pietzcker使用 itertools
: 惰性计算的浮点数范围:
>>> from itertools import count, takewhile
>>> def frange(start, stop, step):
return takewhile(lambda x: x< stop, count(start, step))
>>> list(frange(0.5, 5, 1.5))
# [0.5, 2.0, 3.5]
itertools.takewhile
很好。但是,itertools.count(start, step)
存在累积的浮点误差问题。(例如,计算takewhile(lambda x: x < 100, count(0, 0.1))
。)相反,我会编写takewhile(lambda x: x < stop, (start + i * step for i in count()))
。请注意不要改变原始含义,并尽量使翻译更易于理解。 - musiphilmore_itertools.numeric_range(start, stop, step)
与内置函数 range 类似,但可以处理浮点数、Decimal 和 Fraction 类型。>>> from more_itertools import numeric_range
>>> tuple(numeric_range(.1, 5, 1))
(0.1, 1.1, 2.1, 3.1, 4.1)
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。
list(frange_S(2,3,4)) is [] but should be [2.0]
。 - smichr由kichik提供了一种不依赖numpy等库的解决方案,但由于浮点运算, 它经常表现出意外的行为。正如我自己和blubberdiblub所指出的那样,额外的元素很容易潜入结果中。例如naive_frange(0.0, 1.0, 0.1)
会产生0.999...
作为其最后一个值,因此总共产生11个值。
这里提供了一个稍微健壮的版本:
def frange(x, y, jump=1.0):
'''Range for floats.'''
i = 0.0
x = float(x) # Prevent yielding integers.
x0 = x
epsilon = jump / 2.0
yield x # yield always first value
while x + epsilon < y:
i += 1.0
x = x0 + i * jump
if x < y:
yield x
epsilon
可以处理乘法可能出现的舍入误差,尽管在非常小和非常大的情况下可能会出现问题。现在,正如预期的那样:> a = list(frange(0.0, 1.0, 0.1))
> a[-1]
0.9
> len(a)
10
并且使用稍大的数字:
> b = list(frange(0.0, 1000000.0, 0.1))
> b[-1]
999999.9
> len(b)
10000000
该代码也可作为 GitHub Gist获得。
frange(0.026, 0.619, 0.078)
失败。 - smichrfrange(0.026,0.619,0.078)
得到的结果是0.026,0.104,0.182,0.26,0.338,0.416,0.494,0.572
,就像应该的一样。 - Akseli Palénfrange(.071,.493,.001)
这种情况,它不应以0.493结束。但是,如果您认为是因为当x <= y
时发出信号,则可以将其更改为x <y
,但是那么frange(0.569,0.799,0.23)
将会失败,因为它会发出大于0.569的信号。我正在针对我提供的版本测试代码。 - smichr<=
改为 <
。我想进一步改进已经没有希望了。基本问题仍然存在:在 Python 中,0.569 + 0.23 > 0.799
是一个正确的语句,尽管对于我们习惯于十进制的眼睛来看,它看起来像是错误的。使用 decimal.Decimal
可能是唯一正确的方法,就像 被接受的答案 中所介绍的那样。 - Akseli Palén正如 kichik 所写,这并不应该太复杂。但是,这段代码:
def frange(x, y, jump):
while x < y:
yield x
x += jump
浮点数在处理时由于错误的累计效应而不合适。这就是为什么你会收到类似以下的东西:
>>>list(frange(0, 100, 0.1))[-1]
99.9999999999986
预期的行为应该是:
>>>list(frange(0, 100, 0.1))[-1]
99.9
可以通过使用索引变量来简单减少累积误差。以下是示例:
from math import ceil
def frange2(start, stop, step):
n_items = int(ceil((stop - start) / step))
return (start + i*step for i in range(n_items))
不使用嵌套函数。只需使用while循环和计数器变量:
此示例按预期工作。
def frange3(start, stop, step):
res, n = start, 1
while res < stop:
yield res
res = start + n * step
n += 1
这个函数通常也能正常工作,但在需要翻转区间的情况下不能如此。例如:
>>>list(frange3(1, 0, -.1))
[]
from operator import gt, lt
def frange3(start, stop, step):
res, n = start, 0.
predicate = lt if start < stop else gt
while predicate(res, stop):
yield res
res = start + n * step
n += 1
使用这个技巧,您可以使用负步长(即倒序)来调用这些函数:
>>>list(frange3(1, 0, -.1))
[1, 0.9, 0.8, 0.7, 0.6, 0.5, 0.3999999999999999, 0.29999999999999993, 0.19999999999999996, 0.09999999999999998]
您可以使用纯标准库进一步组合数字类型的范围函数:
from itertools import count
from itertools import takewhile
def any_range(start, stop, step):
start = type(start + step)(start)
return takewhile(lambda n: n < stop, count(start, step))
这个生成器源代码来自于《Fluent Python》一书(第14章 “可迭代对象、迭代器和生成器”)。它不能用于递减的范围。你必须像前一个解决方案中那样应用黑科技。
例如,您可以按以下方式使用此生成器:
>>>list(any_range(Fraction(2, 1), Fraction(100, 1), Fraction(1, 3)))[-1]
299/3
>>>list(any_range(Decimal('2.'), Decimal('4.'), Decimal('.3')))
[Decimal('2'), Decimal('2.3'), Decimal('2.6'), Decimal('2.9'), Decimal('3.2'), Decimal('3.5'), Decimal('3.8')]
当然,你也可以将其与浮点数和整数一起使用。
如果您想使用这些函数进行负步长的操作,则应添加一个步长符号的检查,例如:
no_proceed = (start < stop and step < 0) or (start > stop and step > 0)
if no_proceed: raise StopIteration
如果您想要模仿 range
函数接口,最好的选择是提出 StopIteration
异常。
如果您想要模仿 range
函数接口,您可以提供一些参数检查:
def any_range2(*args):
if len(args) == 1:
start, stop, step = 0, args[0], 1.
elif len(args) == 2:
start, stop, step = args[0], args[1], 1.
elif len(args) == 3:
start, stop, step = args
else:
raise TypeError('any_range2() requires 1-3 numeric arguments')
# here you can check for isinstance numbers.Real or use more specific ABC or whatever ...
start = type(start + step)(start)
return takewhile(lambda n: n < stop, count(start, step))
我觉得你明白了。
你可以使用这些函数中的任意一个(除了第一个函数),而且你所需要的一切都是Python标准库。
range(5, 50, 5)
,然后只需将每个数字除以10。 - NullUserException