Jon*_*han 120 python decimal range fractions
range()
Python中是否有等效的浮点数?
>>> 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
Run Code Online (Sandbox Code Playgroud)
Xae*_*ess 97
你可以使用:
[x / 10.0 for x in range(5, 50, 15)]
Run Code Online (Sandbox Code Playgroud)
或使用lambda/map:
map(lambda x: x/10.0, range(5, 50, 15))
Run Code Online (Sandbox Code Playgroud)
kic*_*hik 90
我不知道一个内置的功能,但是写一个像这样应该不会太复杂.
def frange(x, y, jump):
while x < y:
yield x
x += jump
Run Code Online (Sandbox Code Playgroud)
正如评论所提到的,这可能产生不可预测的结果,如:
>>> list(frange(0, 100, 0.1))[-1]
99.9999999999986
Run Code Online (Sandbox Code Playgroud)
要获得预期结果,您可以使用此问题中的其他答案之一,或者如@Tadhg所述,您可以将其decimal.Decimal
用作jump
参数.确保使用字符串而不是浮点数初始化它.
>>> import decimal
>>> list(frange(0, 100, decimal.Decimal('0.1')))[-1]
Decimal('99.9')
Run Code Online (Sandbox Code Playgroud)
甚至:
import decimal
def drange(x, y, jump):
while x < y:
yield float(x)
x += decimal.Decimal(jump)
Run Code Online (Sandbox Code Playgroud)
然后:
>>> list(drange(0, 100, '0.1'))[-1]
99.9
Run Code Online (Sandbox Code Playgroud)
wim*_*wim 72
我曾经使用numpy.arange
但由于浮点错误而控制返回的元素数量有一些复杂性.所以我现在使用linspace
,例如:
>>> import numpy
>>> numpy.linspace(0, 10, num=4)
array([ 0. , 3.33333333, 6.66666667, 10. ])
Run Code Online (Sandbox Code Playgroud)
Pat*_*Pat 38
Pylab有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. ])
Run Code Online (Sandbox Code Playgroud)
Kar*_*tel 10
热切评价(2.x range
):
[x * .5 for x in range(10)]
Run Code Online (Sandbox Code Playgroud)
懒惰评价(2.x xrange
,3.x range
):
itertools.imap(lambda x: x * .5, xrange(10)) # or range(10) as appropriate
Run Code Online (Sandbox Code Playgroud)
交替:
itertools.islice(itertools.imap(lambda x: x * .5, itertools.count()), 10)
# without applying the `islice`, we get an infinite stream of half-integers.
Run Code Online (Sandbox Code Playgroud)
使用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]
Run Code Online (Sandbox Code Playgroud)
正如kichik所写,这不应该太复杂。然而这段代码:
def frange(x, y, jump):
while x < y:
yield x
x += jump
Run Code Online (Sandbox Code Playgroud)
这是不合适的,因为使用浮动时错误的累积效应。这就是为什么您会收到类似以下内容的原因:
>>>list(frange(0, 100, 0.1))[-1]
99.9999999999986
Run Code Online (Sandbox Code Playgroud)
虽然预期的行为是:
>>>list(frange(0, 100, 0.1))[-1]
99.9
Run Code Online (Sandbox Code Playgroud)
通过使用索引变量可以简单地减少累积误差。这是例子:
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))
Run Code Online (Sandbox Code Playgroud)
该示例按预期工作。
没有嵌套函数。只有一段时间和一个计数器变量:
def frange3(start, stop, step):
res, n = start, 1
while res < stop:
yield res
res = start + n * step
n += 1
Run Code Online (Sandbox Code Playgroud)
除了您想要反转范围的情况外,此功能也可以很好地工作。例如:
>>>list(frange3(1, 0, -.1))
[]
Run Code Online (Sandbox Code Playgroud)
在这种情况下,解决方案 1 将按预期工作。要使此功能在这种情况下工作,您必须应用类似于以下内容的 hack:
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
Run Code Online (Sandbox Code Playgroud)
通过这个 hack,你可以使用这些带有负步骤的函数:
>>>list(frange3(1, 0, -.1))
[1, 0.9, 0.8, 0.7, 0.6, 0.5, 0.3999999999999999, 0.29999999999999993, 0.19999999999999996, 0.09999999999999998]
Run Code Online (Sandbox Code Playgroud)
您可以使用普通标准库更进一步,为大多数数字类型编写范围函数:
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))
Run Code Online (Sandbox Code Playgroud)
该生成器改编自 Fluent Python 书(第 14 章:迭代器、迭代器和生成器)。它不适用于递减的范围。您必须应用 hack,就像之前的解决方案一样。
您可以按如下方式使用该生成器,例如:
>>>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')]
Run Code Online (Sandbox Code Playgroud)
当然,您也可以将它与float和int一起使用。
如果您想在负步骤中使用这些函数,您应该添加对步骤符号的检查,例如:
no_proceed = (start < stop and step < 0) or (start > stop and step > 0)
if no_proceed: raise StopIteration
Run Code Online (Sandbox Code Playgroud)
StopIteration
如果您想模仿函数本身,最好的选择是 raise range
。
如果您想模仿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))
Run Code Online (Sandbox Code Playgroud)
我想,你说得对。您可以使用这些函数中的任何一个(除了第一个函数),并且您只需要 python 标准库即可。
小智 6
我不知道这个问题是否已经过时,但库中有一个arange
函数NumPy
,它可以作为一个范围。
np.arange(0,1,0.1)
#out:
array([0. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9])
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
因为乘法,舍入误差不会累积。使用 可以epsilon
解决乘法可能出现的舍入误差,即使在非常小和非常大的末端可能会出现问题。现在,正如预期的那样:
> a = list(frange(0.0, 1.0, 0.1))
> a[-1]
0.9
> len(a)
10
Run Code Online (Sandbox Code Playgroud)
对于更大的数字:
> b = list(frange(0.0, 1000000.0, 0.1))
> b[-1]
999999.9
> len(b)
10000000
Run Code Online (Sandbox Code Playgroud)
该代码也可以作为GitHub Gist提供。
没有这样的内置函数,但您可以使用以下(Python 3 代码)像 Python 允许的那样安全地完成这项工作。
from fractions import Fraction
def frange(start, stop, jump, end=False, via_str=False):
"""
Equivalent of Python 3 range for decimal numbers.
Notice that, because of arithmetic errors, it is safest to
pass the arguments as strings, so they can be interpreted to exact fractions.
>>> assert Fraction('1.1') - Fraction(11, 10) == 0.0
>>> assert Fraction( 0.1 ) - Fraction(1, 10) == Fraction(1, 180143985094819840)
Parameter `via_str` can be set to True to transform inputs in strings and then to fractions.
When inputs are all non-periodic (in base 10), even if decimal, this method is safe as long
as approximation happens beyond the decimal digits that Python uses for printing.
For example, in the case of 0.1, this is the case:
>>> assert str(0.1) == '0.1'
>>> assert '%.50f' % 0.1 == '0.10000000000000000555111512312578270211815834045410'
If you are not sure whether your decimal inputs all have this property, you are better off
passing them as strings. String representations can be in integer, decimal, exponential or
even fraction notation.
>>> assert list(frange(1, 100.0, '0.1', end=True))[-1] == 100.0
>>> assert list(frange(1.0, '100', '1/10', end=True))[-1] == 100.0
>>> assert list(frange('1', '100.0', '.1', end=True))[-1] == 100.0
>>> assert list(frange('1.0', 100, '1e-1', end=True))[-1] == 100.0
>>> assert list(frange(1, 100.0, 0.1, end=True))[-1] != 100.0
>>> assert list(frange(1, 100.0, 0.1, end=True, via_str=True))[-1] == 100.0
"""
if via_str:
start = str(start)
stop = str(stop)
jump = str(jump)
start = Fraction(start)
stop = Fraction(stop)
jump = Fraction(jump)
while start < stop:
yield float(start)
start += jump
if end and start == stop:
yield(float(start))
Run Code Online (Sandbox Code Playgroud)
您可以通过运行一些断言来验证所有这些:
assert Fraction('1.1') - Fraction(11, 10) == 0.0
assert Fraction( 0.1 ) - Fraction(1, 10) == Fraction(1, 180143985094819840)
assert str(0.1) == '0.1'
assert '%.50f' % 0.1 == '0.10000000000000000555111512312578270211815834045410'
assert list(frange(1, 100.0, '0.1', end=True))[-1] == 100.0
assert list(frange(1.0, '100', '1/10', end=True))[-1] == 100.0
assert list(frange('1', '100.0', '.1', end=True))[-1] == 100.0
assert list(frange('1.0', 100, '1e-1', end=True))[-1] == 100.0
assert list(frange(1, 100.0, 0.1, end=True))[-1] != 100.0
assert list(frange(1, 100.0, 0.1, end=True, via_str=True))[-1] == 100.0
assert list(frange(2, 3, '1/6', end=True))[-1] == 3.0
assert list(frange(0, 100, '1/3', end=True))[-1] == 100.0
Run Code Online (Sandbox Code Playgroud)
GitHub 上提供的代码
我帮助将函数numeric_range添加到包more-itertools中。
more_itertools.numeric_range(start, stop, step)
行为类似于内置函数范围,但可以处理浮点数,小数和小数类型。
>>> from more_itertools import numeric_range
>>> tuple(numeric_range(.1, 5, 1))
(0.1, 1.1, 2.1, 3.1, 4.1)
Run Code Online (Sandbox Code Playgroud)
为什么标准库中没有浮点范围实现?
\n正如这里所有帖子所明确的那样,没有浮点版本的range()
. 也就是说,如果我们考虑到该range()
函数经常用作索引(当然,这意味着访问器)生成器,则省略是有意义的。因此,当我们调用 时range(0,40)
,我们实际上是在说我们想要 40 个值,从 0 开始,一直到 40,但不包括 40 本身。
当我们认为索引生成与索引的数量和索引的值一样重要时,range()
在标准库中使用 float 实现就没那么有意义了。例如,如果我们调用函数frange(0, 10, 0.25)
,我们期望同时包含 0 和 10,但这将产生一个具有 41 个值的生成器,而不是 期望的 40 个值10/0.25
。
因此,根据其用途,frange()
函数总是会表现出违反直觉的行为。它要么从索引角度来看具有太多值,要么不包含从数学角度合理返回的数字。换句话说,很容易看出这样的函数如何将两个截然不同的用例混为一谈 \xe2\x80\x93 命名暗示了索引用例;这种行为暗示着一种数学行为。
数学用例
\n话虽如此,正如其他帖子中所讨论的,numpy.linspace()
从数学角度很好地执行了生成:
numpy.linspace(0, 10, 41)\narray([ 0. , 0.25, 0.5 , 0.75, 1. , 1.25, 1.5 , 1.75,\n 2. , 2.25, 2.5 , 2.75, 3. , 3.25, 3.5 , 3.75,\n 4. , 4.25, 4.5 , 4.75, 5. , 5.25, 5.5 , 5.75,\n 6. , 6.25, 6.5 , 6.75, 7. , 7.25, 7.5 , 7.75,\n 8. , 8.25, 8.5 , 8.75, 9. , 9.25, 9.5 , 9.75, 10.\n])\n
Run Code Online (Sandbox Code Playgroud)\n索引用例
\n对于索引的角度,我编写了一种稍微不同的方法,其中包含一些技巧性的字符串魔术,使我们能够指定小数位数。
\n# Float range function - string formatting method\ndef frange_S (start, stop, skip = 1.0, decimals = 2):\n for i in range(int(start / skip), int(stop / skip)):\n yield float(("%0." + str(decimals) + "f") % (i * skip))\n
Run Code Online (Sandbox Code Playgroud)\n同样,我们也可以使用内置round
函数并指定小数位数:
# Float range function - rounding method\ndef frange_R (start, stop, skip = 1.0, decimals = 2):\n for i in range(int(start / skip), int(stop / skip)):\n yield round(i * skip, ndigits = decimals)\n
Run Code Online (Sandbox Code Playgroud)\n快速比较和性能
\n当然,考虑到上述讨论,这些函数的用例相当有限。尽管如此,这里还是一个快速比较:
\ndef compare_methods (start, stop, skip):\n\n string_test = frange_S(start, stop, skip)\n round_test = frange_R(start, stop, skip)\n\n for s, r in zip(string_test, round_test):\n print(s, r)\n\ncompare_methods(-2, 10, 1/3)\n
Run Code Online (Sandbox Code Playgroud)\n每个结果都是相同的:
\n-2.0 -2.0\n-1.67 -1.67\n-1.33 -1.33\n-1.0 -1.0\n-0.67 -0.67\n-0.33 -0.33\n0.0 0.0\n...\n8.0 8.0\n8.33 8.33\n8.67 8.67\n9.0 9.0\n9.33 9.33\n9.67 9.67\n
Run Code Online (Sandbox Code Playgroud)\n还有一些时间安排:
\n>>> import timeit\n\n>>> setup = """\n... def frange_s (start, stop, skip = 1.0, decimals = 2):\n... for i in range(int(start / skip), int(stop / skip)):\n... yield float(("%0." + str(decimals) + "f") % (i * skip))\n... def frange_r (start, stop, skip = 1.0, decimals = 2):\n... for i in range(int(start / skip), int(stop / skip)):\n... yield round(i * skip, ndigits = decimals)\n... start, stop, skip = -1, 8, 1/3\n... """\n\n>>> min(timeit.Timer('string_test = frange_s(start, stop, skip); [x for x in string_test]', setup=setup).repeat(30, 1000))\n0.024284090992296115\n\n>>> min(timeit.Timer('round_test = frange_r(start, stop, skip); [x for x in round_test]', setup=setup).repeat(30, 1000))\n0.025324633985292166\n
Run Code Online (Sandbox Code Playgroud)\n看起来字符串格式化方法在我的系统上以微弱优势获胜。
\n局限性
\n最后,演示上述讨论的要点和最后一个限制:
\n# "Missing" the last value (10.0)\nfor x in frange_R(0, 10, 0.25):\n print(x)\n\n0.25\n0.5\n0.75\n1.0\n...\n9.0\n9.25\n9.5\n9.75\n
Run Code Online (Sandbox Code Playgroud)\n此外,当skip
参数不能被stop
值整除时,考虑到后一个问题,可能会出现巨大的差距:
# Clearly we know that 10 - 9.43 is equal to 0.57\nfor x in frange_R(0, 10, 3/7):\n print(x)\n\n0.0\n0.43\n0.86\n1.29\n...\n8.14\n8.57\n9.0\n9.43\n
Run Code Online (Sandbox Code Playgroud)\n有多种方法可以解决这个问题,但归根结底,最好的方法可能是只使用 Numpy。
\n 归档时间: |
|
查看次数: |
206084 次 |
最近记录: |