范围()浮动

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)

  • 永远不要使用numpy.arange(numpy文档本身建议不要使用它).使用wim推荐的numpy.linspace,或者本答案中的其他建议之一. (3认同)
  • @edvaldig:你是对的,我不知道这个......不过我认为`arange(0.5,5,1.5)`是IMO更具可读性. (2认同)
  • 我更喜欢这个答案而不是接受的答案,因为前两个解决方案是基于迭代整数并从整数中导出最终浮点数.这更加强大.如果直接使用浮点数进行操作,则由于内部如何表示浮点数,您可能会出现奇怪的一次性错误.例如,如果你尝试`list(frange(0,1,0.5))`,它工作正常,1被排除,但如果你尝试`list(frange(0,1,0.1))`,最后一个值你get接近1.0,这可能不是你想要的.这里介绍的解决方案没有这个问题. (2认同)

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)

  • Python的座右铭实际上是[应该有一个 - 最好只有一个 - 显而易见的方式](http://www.python.org/dev/peps/pep-0020/).但是反正Python很棒:) (32认同)
  • -1**请不要使用此代码**,至少不要使用可能影响我生活的软件.没有办法使它可靠地工作.不要使用AkseliPalén的答案.使用Xaerxess或wim的答案(除了忽略关于arange的部分). (9认同)
  • @Jonathan:他可能不是荷兰人:P (7认同)
  • `>>>打印列表(frange(0,100,0.1))[ - 1] == 100.0`将为'False` (3认同)
  • 如果你使用`decimal.Decimal`**作为步骤而不是浮点数,这个效果很好**. (3认同)
  • 对于否定的“跳转”无效 (2认同)

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)

  • @TNT不,那不是错误。您会发现`np.linspace(-。1,10,num = 5050)[0] == -.1`是True。只是`repr(np.float64('-0.1'))`显示更多数字。 (2认同)
  • 虽然该特定示例显示没有过多的舍入误差,但也存在失败案例。例如,当理想结果为“1.0”时,“print(numpy.linspace(0, 3, 148)[49])”会打印“0.9999999999999999”。`linspace` 比 `arange` 做得更好,但不能保证产生最小可能的舍入误差。 (2认同)

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)

  • 从matplotlib 2.2版开始,不再推荐使用Frange。应该使用numpy.arange。 (4认同)

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)

  • +1; 但为什么``(x*.5 for x in range(10))`作为懒惰评估的生成器表达式? (4认同)
  • 因为那太简单了,我猜?:) (2认同)

Ayu*_*ush 8

使用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)

  • +1使用`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()) )``相反. (3认同)

sto*_*pa4 6

正如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)

解决方案1

通过使用索引变量可以简单地减少累积误差。这是例子:

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)

该示例按预期工作。

解决方案2

没有嵌套函数。只有一段时间和一个计数器变量:

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)

解决方案3

您可以使用普通标准库更进一步,为大多数数字类型编写范围函数:

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)

当然,您也可以将它与floatint一起使用。

当心

如果您想在负步骤中使用这些函数,您应该添加对步骤符号的检查,例如:

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)


Aks*_*lén 5

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提供。


mar*_*ama 5

没有这样的内置函数,但您可以使用以下(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 上提供的代码


Wil*_*ack 5

我帮助将函数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)


Gre*_*ick 5

为什么标准库中没有浮点范围实现?

\n

正如这里所有帖子所明确的那样,没有浮点版本的range(). 也就是说,如果我们考虑到该range()函数经常用作索引(当然,这意味着访问器)生成器,则省略是有意义的。因此,当我们调用 时range(0,40),我们实际上是在说我们想要 40 个值,从 0 开始,一直到 40,但不包括 40 本身。

\n

当我们认为索引生成与索引的数量和索引的值一样重要时,range()在标准库中使用 float 实现就没那么有意义了。例如,如果我们调用函数frange(0, 10, 0.25),我们期望同时包含 0 和 10,但这将产生一个具有 41 个值的生成器,而不是 期望的 40 个值10/0.25

\n

因此,根据其用途,frange()函数总是会表现出违反直觉的行为。它要么从索引角度来看具有太多值,要么不包含从数学角度合理返回的数字。换句话说,很容易看出这样的函数如何将两个截然不同的用例混为一谈 \xe2\x80\x93 命名暗示了索引用例;这种行为暗示着一种数学行为。

\n

数学用例

\n

话虽如此,正如其他帖子中所讨论的,numpy.linspace()从数学角度很好地执行了生成:

\n
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函数并指定小数位数:

\n
# 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

当然,考虑到上述讨论,这些函数的用例相当有限。尽管如此,这里还是一个快速比较:

\n
def 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值整除时,考虑到后一个问题,可能会出现巨大的差距:

\n
# 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

  • 这是一个相当扭曲的论点。range() 应该简单地查看迭代生成器,并且它是否用于 for 循环或索引某些内容应该留给调用者。人们在 for 循环中使用浮点数已有数千年之久,上述理由是无意义的。Python 委员会的人们在这里搞砸了,好的争论可能会被上面的一些扭曲的理由淹没。就是这么简单明了。现在,Python 语言中包含了太多类似上述的决定。 (2认同)