问题:浮点数的range()

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

Is there a range() equivalent for floats in 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

回答 0

我不知道一个内置的功能,但是写一个像这样应该不会太复杂。

def 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

I don’t know a built-in function, but writing one like this shouldn’t be too complicated.

def frange(x, y, jump):
  while x < y:
    yield x
    x += jump

As the comments mention, this could produce unpredictable results like:

>>> list(frange(0, 100, 0.1))[-1]
99.9999999999986

To get the expected result, you can use one of the other answers in this question, or as @Tadhg mentioned, you can use decimal.Decimal as the jump argument. Make sure to initialize it with a string rather than a float.

>>> import decimal
>>> list(frange(0, 100, decimal.Decimal('0.1')))[-1]
Decimal('99.9')

Or even:

import decimal

def drange(x, y, jump):
  while x < y:
    yield float(x)
    x += decimal.Decimal(jump)

And then:

>>> list(drange(0, 100, '0.1'))[-1]
99.9

回答 1

您可以使用:

[x / 10.0 for x in range(5, 50, 15)]

或使用lambda / map:

map(lambda x: x/10.0, range(5, 50, 15))

You can either use:

[x / 10.0 for x in range(5, 50, 15)]

or use lambda / map:

map(lambda x: x/10.0, range(5, 50, 15))

回答 2

我曾经使用过,numpy.arange但是由于浮点错误,控制返回的元素数量有些复杂。所以现在我使用linspace,例如:

>>> import numpy
>>> numpy.linspace(0, 10, num=4)
array([  0.        ,   3.33333333,   6.66666667,  10.        ])

I used to use numpy.arange but had some complications controlling the number of elements it returns, due to floating point errors. So now I use linspace, e.g.:

>>> import numpy
>>> numpy.linspace(0, 10, num=4)
array([  0.        ,   3.33333333,   6.66666667,  10.        ])

回答 3

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. ])

Pylab has frange (a wrapper, actually, for 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. ])

回答 4

经过认真评估(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.

Eagerly evaluated (2.x range):

[x * .5 for x in range(10)]

Lazily evaluated (2.x xrange, 3.x range):

itertools.imap(lambda x: x * .5, xrange(10)) # or range(10) as appropriate

Alternately:

itertools.islice(itertools.imap(lambda x: x * .5, itertools.count()), 10)
# without applying the `islice`, we get an infinite stream of half-integers.

回答 5

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

using itertools: lazily evaluated floating point range:

>>> 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]

回答 6

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

I helped add the function numeric_range to the package more-itertools.

more_itertools.numeric_range(start, stop, step) acts like the built in function range but can handle floats, Decimal, and Fraction types.

>>> from more_itertools import numeric_range
>>> tuple(numeric_range(.1, 5, 1))
(0.1, 1.1, 2.1, 3.1, 4.1)

回答 7

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

您可以通过运行一些断言来验证所有这些内容:

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

GitHub上的代码

There is no such built-in function, but you can use the following (Python 3 code) to do the job as safe as Python allows you to.

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

You can verify all of it by running a few assertions:

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

Code available on GitHub


回答 8

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

如此处所有帖子所述,没有的浮点版本range()。就是说,如果我们认为range()函数经常用作索引生成器(当然,这意味着访问器),则省略是有意义的。因此,当我们调用时range(0,40),实际上是说我们想要40个值,从0开始,最多40个,但不包括40个本身。

当我们认为索引的生成与其值的数量一样多时,range()在标准库中使用float实现就没有意义了。例如,如果我们调用该函数frange(0, 10, 0.25),我们希望同时包含0和10,但这将产生一个具有41个值的向量。

因此,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。

Why Is There No Floating Point Range Implementation In The Standard Library?

As made clear by all the posts here, there is no floating point version of range(). That said, the omission makes sense if we consider that the range() function is often used as an index (and of course, that means an accessor) generator. So, when we call range(0,40), we’re in effect saying we want 40 values starting at 0, up to 40, but non-inclusive of 40 itself.

When we consider that index generation is as much about the number of indices as it is their values, the use of a float implementation of range() in the standard library makes less sense. For example, if we called the function frange(0, 10, 0.25), we would expect both 0 and 10 to be included, but that would yield a vector with 41 values.

Thus, an frange() function depending on its use will always exhibit counter intuitive behavior; it either has too many values as perceived from the indexing perspective or is not inclusive of a number that reasonably should be returned from the mathematical perspective.

The Mathematical Use Case

With that said, as discussed, numpy.linspace() performs the generation with the mathematical perspective nicely:

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.
])

The Indexing Use Case

And for the indexing perspective, I’ve written a slightly different approach with some tricksy string magic that allows us to specify the number of decimal places.

# 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))

Similarly, we can also use the built-in round function and specify the number of decimals:

# 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)

A Quick Comparison & Performance

Of course, given the above discussion, these functions have a fairly limited use case. Nonetheless, here’s a quick comparison:

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)

The results are identical for each:

-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

And some timings:

>>> 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

Looks like the string formatting method wins by a hair on my system.

The Limitations

And finally, a demonstration of the point from the discussion above and one last limitation:

# "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

Further, when the skip parameter is not divisible by the stop value, there can be a yawning gap given the latter issue:

# 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

There are ways to address this issue, but at the end of the day, the best approach would probably be to just use Numpy.


回答 9

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
        yield x

由于相乘,舍入误差不会累积。epsilon即使问题可能会在很小的一端和很大的端头出现,使用use 也会照顾到乘法的舍入误差。现在,正如预期的那样:

> 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获得

A solution without numpy etc dependencies was provided by kichik but due to the floating point arithmetics, it often behaves unexpectedly. As noted by me and blubberdiblub, additional elements easily sneak into the result. For example naive_frange(0.0, 1.0, 0.1) would yield 0.999... as its last value and thus yield 11 values in total.

A robust version is provided here:

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
        yield x

Because the multiplication, the rounding errors do not accumulate. The use of epsilon takes care of possible rounding error of the multiplication, even though issues of course might rise in the very small and very large ends. Now, as expected:

> a = list(frange(0.0, 1.0, 0.1))
> a[-1]
0.9
> len(a)
10

And with somewhat larger numbers:

> b = list(frange(0.0, 1000000.0, 0.1))
> b[-1]
999999.9
> len(b)
10000000

The code is also available as a GitHub Gist.


回答 10

一个更简单的无库版本

哎呀,我会扔一个简单的无库版本。随时进行改进[*]:

def frange(start=0, stop=1, jump=0.1):
    nsteps = int((stop-start)/jump)
    dy = stop-start
    # f(i) goes from start to stop as i goes from 0 to nsteps
    return [start + float(i)*dy/nsteps for i in range(nsteps)]

核心思想是使nsteps您从头到尾的步骤数,并且range(nsteps)始终发出整数,因此不会损失准确性。最后一步是将[0..nsteps]线性映射到[start..stop]。

编辑

如果您像alancalvitti一样希望系列具有精确的有理表示,则可以随时使用Fractions

from fractions import Fraction

def rrange(start=0, stop=1, jump=0.1):
    nsteps = int((stop-start)/jump)
    return [Fraction(i, nsteps) for i in range(nsteps)]

[*]特别是frange()返回列表,而不是生成器。但这足以满足我的需求。

A simpler library-less version

Aw, heck — I’ll toss in a simple library-less version. Feel free to improve on it[*]:

def frange(start=0, stop=1, jump=0.1):
    nsteps = int((stop-start)/jump)
    dy = stop-start
    # f(i) goes from start to stop as i goes from 0 to nsteps
    return [start + float(i)*dy/nsteps for i in range(nsteps)]

The core idea is that nsteps is the number of steps to get you from start to stop and range(nsteps) always emits integers so there’s no loss of accuracy. The final step is to map [0..nsteps] linearly onto [start..stop].

edit

If, like alancalvitti you’d like the series to have exact rational representation, you can always use Fractions:

from fractions import Fraction

def rrange(start=0, stop=1, jump=0.1):
    nsteps = int((stop-start)/jump)
    return [Fraction(i, nsteps) for i in range(nsteps)]

[*] In particular, frange() returns a list, not a generator. But it sufficed for my needs.


回答 11

这可以通过numpy.arange(start,stop,stepsize)完成

import numpy as np

np.arange(0.5,5,1.5)
>> [0.5, 2.0, 3.5, 5.0]

# OBS you will sometimes see stuff like this happening, 
# so you need to decide whether that's not an issue for you, or how you are going to catch it.
>> [0.50000001, 2.0, 3.5, 5.0]

注意1:在此处评论部分的讨论中,“请勿使用numpy.arange()(numpy文档本身建议不要使用它。请按照wim的建议使用numpy.linspace,或此答案中的其他建议之一”)

注意2: 我已在此处阅读了一些评论中的讨论,但是在第三次回到这个问题之后,我认为此信息应该放在更易读的位置。

This can be done with numpy.arange(start, stop, stepsize)

import numpy as np

np.arange(0.5,5,1.5)
>> [0.5, 2.0, 3.5, 5.0]

# OBS you will sometimes see stuff like this happening, 
# so you need to decide whether that's not an issue for you, or how you are going to catch it.
>> [0.50000001, 2.0, 3.5, 5.0]

Note 1: From the discussion in the comment section here, “never use numpy.arange() (the numpy documentation itself recommends against it). Use numpy.linspace as recommended by wim, or one of the other suggestions in this answer”

Note 2: I have read the discussion in a few comments here, but after coming back to this question for the third time now, I feel this information should be placed in a more readable position.


回答 12

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

解决方案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))

本示例按预期方式工作。

解决方案2

没有嵌套函数。只有一会儿和一个计数器变量:

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))
[]

在这种情况下,解决方案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]

解决方案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))

该生成器改编自Fluent Python书(第14章)。Iterables,迭代器和生成器。缩小范围将不起作用。您必须像以前的解决方案一样使用黑客手段。

您可以按如下方式使用此生成器,例如:

>>>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')]

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

小心

如果要将这些功能用于否定步骤,则应添加对步骤符号的检查,例如:

no_proceed = (start < stop and step < 0) or (start > stop and step > 0)
if no_proceed: raise StopIteration

StopIteration如果要模拟range功能本身,最好的方法是raise 。

模拟范围

如果您想模仿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标准库。

As kichik wrote, this shouldn’t be too complicated. However this code:

def frange(x, y, jump):
  while x < y:
    yield x
    x += jump

Is inappropriate because of the cumulative effect of errors when working with floats. That is why you receive something like:

>>>list(frange(0, 100, 0.1))[-1]
99.9999999999986

While the expected behavior would be:

>>>list(frange(0, 100, 0.1))[-1]
99.9

Solution 1

The cumulative error can simply be reduced by using an index variable. Here’s the example:

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

This example works as expected.

Solution 2

No nested functions. Only a while and a counter variable:

def frange3(start, stop, step):
    res, n = start, 1

    while res < stop:
        yield res
        res = start + n * step
        n += 1

This function will work well too, except for the cases when you want the reversed range. E.g:

>>>list(frange3(1, 0, -.1))
[]

Solution 1 in this case will work as expected. To make this function work in such situations, you must apply a hack, similar to the following:

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

With this hack you can use these functions with negative steps:

>>>list(frange3(1, 0, -.1))
[1, 0.9, 0.8, 0.7, 0.6, 0.5, 0.3999999999999999, 0.29999999999999993, 0.19999999999999996, 0.09999999999999998]

Solution 3

You can go even further with plain standard library and compose a range function for the most of numeric types:

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

This generator is adapted from the Fluent Python book (Chapter 14. Iterables, Iterators and generators). It will not work with decreasing ranges. You must apply a hack, like in the previous solution.

You can use this generator as follows, for example:

>>>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')]

And of course you can use it with float and int as well.

Be careful

If you want to use these functions with negative steps, you should add a check for the step sign, e.g.:

no_proceed = (start < stop and step < 0) or (start > stop and step > 0)
if no_proceed: raise StopIteration

The best option here is to raise StopIteration, if you want to mimic the range function itself.

Mimic range

If you would like to mimic the range function interface, you can provide some argument checks:

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

I think, you’ve got the point. You can go with any of these functions (except the very first one) and all you need for them is python standard library.


回答 13

我编写了一个函数,该函数返回范围为双精度浮点数的元组,且没有超过百分之一的小数位。只需解析诸如字符串之类的范围值并将剩余部分分开即可。我用它来显示要从UI中选择的范围。我希望其他人觉得它有用。

def drange(start,stop,step):
    double_value_range = []
    while start<stop:
        a = str(start)
        a.split('.')[1].split('0')[0]
        start = float(str(a))
        double_value_range.append(start)
        start = start+step
    double_value_range_tuple = tuple(double_value_range)
   #print double_value_range_tuple
    return double_value_range_tuple

i wrote a function that returns a tuple of a range of double precision floating point numbers without any decimal places beyond the hundredths. it was simply a matter of parsing the range values like strings and splitting off the excess. I use it for displaying ranges to select from within a UI. I hope someone else finds it useful.

def drange(start,stop,step):
    double_value_range = []
    while start<stop:
        a = str(start)
        a.split('.')[1].split('0')[0]
        start = float(str(a))
        double_value_range.append(start)
        start = start+step
    double_value_range_tuple = tuple(double_value_range)
   #print double_value_range_tuple
    return double_value_range_tuple

回答 14

用法

# Counting up
drange(0, 0.4, 0.1)
[0, 0.1, 0.2, 0.30000000000000004, 0.4]

# Counting down
drange(0, -0.4, -0.1)
[0, -0.1, -0.2, -0.30000000000000004, -0.4]

将每一步四舍五入到N个小数位

drange(0, 0.4, 0.1, round_decimal_places=4)
[0, 0.1, 0.2, 0.3, 0.4]

drange(0, -0.4, -0.1, round_decimal_places=4)
[0, -0.1, -0.2, -0.3, -0.4]

def drange(start, end, increment, round_decimal_places=None):
    result = []
    if start < end:
        # Counting up, e.g. 0 to 0.4 in 0.1 increments.
        if increment < 0:
            raise Exception("Error: When counting up, increment must be positive.")
        while start <= end:
            result.append(start)
            start += increment
            if round_decimal_places is not None:
                start = round(start, round_decimal_places)
    else:
        # Counting down, e.g. 0 to -0.4 in -0.1 increments.
        if increment > 0:
            raise Exception("Error: When counting down, increment must be negative.")
        while start >= end:
            result.append(start)
            start += increment
            if round_decimal_places is not None:
                start = round(start, round_decimal_places)
    return result

为什么选择这个答案?

  • 当要求倒计时时,许多其他答案将挂起。
  • 许多其他答案将给出错误的舍入结果。
  • 其他答案np.linspace都是反复无常的,由于难以选择正确的除法数,它们可能会起作用,也可能不会起作用。np.linspace十进制增量为0.1确实很困难,公式中将增量转换为多个拆分的除法顺序可能会导致代码正确或损坏。
  • np.arange不建议使用其他答案。

如有疑问,请尝试上述四个测试案例。

Usage

# Counting up
drange(0, 0.4, 0.1)
[0, 0.1, 0.2, 0.30000000000000004, 0.4]

# Counting down
drange(0, -0.4, -0.1)
[0, -0.1, -0.2, -0.30000000000000004, -0.4]

To round each step to N decimal places

drange(0, 0.4, 0.1, round_decimal_places=4)
[0, 0.1, 0.2, 0.3, 0.4]

drange(0, -0.4, -0.1, round_decimal_places=4)
[0, -0.1, -0.2, -0.3, -0.4]

Code

def drange(start, end, increment, round_decimal_places=None):
    result = []
    if start < end:
        # Counting up, e.g. 0 to 0.4 in 0.1 increments.
        if increment < 0:
            raise Exception("Error: When counting up, increment must be positive.")
        while start <= end:
            result.append(start)
            start += increment
            if round_decimal_places is not None:
                start = round(start, round_decimal_places)
    else:
        # Counting down, e.g. 0 to -0.4 in -0.1 increments.
        if increment > 0:
            raise Exception("Error: When counting down, increment must be negative.")
        while start >= end:
            result.append(start)
            start += increment
            if round_decimal_places is not None:
                start = round(start, round_decimal_places)
    return result

Why choose this answer?

  • Many other answers will hang when asked to count down.
  • Many other answers will give incorrectly rounded results.
  • Other answers based on np.linspace are hit-and-miss, they may or may not work due to difficulty in choosing the correct number of divisions. np.linspace really struggles with decimal increments of 0.1, and the order of divisions in the formula to convert the increment into a number of splits can result in either correct or broken code.
  • Other answers based on np.arange are deprecated.

If in doubt, try the four tests cases above.


回答 15

def Range(*argSequence):
    if len(argSequence) == 3:
        imin = argSequence[0]; imax = argSequence[1]; di = argSequence[2]
        i = imin; iList = []
        while i <= imax:
            iList.append(i)
            i += di
        return iList
    if len(argSequence) == 2:
        return Range(argSequence[0], argSequence[1], 1)
    if len(argSequence) == 1:
        return Range(1, argSequence[0], 1)

请注意,Range的首字母为大写。Python中的函数不建议使用此命名方法。您可以根据需要将Range更改为drange或frange。“范围”功能的行为与您想要的一样。您可以在这里查看它的手册[ http://reference.wolfram.com/language/ref/Range.html ]。

def Range(*argSequence):
    if len(argSequence) == 3:
        imin = argSequence[0]; imax = argSequence[1]; di = argSequence[2]
        i = imin; iList = []
        while i <= imax:
            iList.append(i)
            i += di
        return iList
    if len(argSequence) == 2:
        return Range(argSequence[0], argSequence[1], 1)
    if len(argSequence) == 1:
        return Range(1, argSequence[0], 1)

Please note the first letter of Range is capital. This naming method is not encouraged for functions in Python. You can change Range to something like drange or frange if you want. The “Range” function behaves just as you want it to. You can check it’s manual here [ http://reference.wolfram.com/language/ref/Range.html ].


回答 16

我认为有一个非常简单的答案可以真正模拟范围的所有功能,但对于浮点数和整数而言。在此解决方案中,您仅假设默认情况下的近似值为1e-7(或您选择的近似值),并且可以在调用函数时对其进行更改。

def drange(start,stop=None,jump=1,approx=7): # Approx to 1e-7 by default
  '''
  This function is equivalent to range but for both float and integer
  '''
  if not stop: # If there is no y value: range(x)
      stop= start
      start= 0
  valor= round(start,approx)
  while valor < stop:
      if valor==int(valor):
          yield int(round(valor,approx))
      else:
          yield float(round(valor,approx))
      valor += jump
  for i in drange(12):
      print(i)

I think that there is a very simple answer that really emulates all the features of range but for both float and integer. In this solution, you just suppose that your approximation by default is 1e-7 (or the one you choose) and you can change it when you call the function.

def drange(start,stop=None,jump=1,approx=7): # Approx to 1e-7 by default
  '''
  This function is equivalent to range but for both float and integer
  '''
  if not stop: # If there is no y value: range(x)
      stop= start
      start= 0
  valor= round(start,approx)
  while valor < stop:
      if valor==int(valor):
          yield int(round(valor,approx))
      else:
          yield float(round(valor,approx))
      valor += jump
  for i in drange(12):
      print(i)

回答 17

当然会有一些舍入错误,所以这不是完美的,但这是我通常用于不需要高精度的应用程序的方法。如果要使其更准确,可以添加一个额外的参数来指定如何处理舍入错误。传递舍入函数也许可以使其扩展,并允许程序员指定如何处理舍入错误。

arange = lambda start, stop, step: [i + step * i for i in range(int((stop - start) / step))]

如果我写:

arange(0, 1, 0.1)

它将输出:

[0.0, 0.1, 0.2, 0.30000000000000004, 0.4, 0.5, 0.6000000000000001, 0.7000000000000001, 0.8, 0.9]

There will be of course some rounding errors, so this is not perfect, but this is what I use generally for applications, which don’t require high precision. If you wanted to make this more accurate, you could add an extra argument to specify how to handle rounding errors. Perhaps passing a rounding function might make this extensible and allow the programmer to specify how to handle rounding errors.

arange = lambda start, stop, step: [i + step * i for i in range(int((stop - start) / step))]

If I write:

arange(0, 1, 0.1)

It will output:

[0.0, 0.1, 0.2, 0.30000000000000004, 0.4, 0.5, 0.6000000000000001, 0.7000000000000001, 0.8, 0.9]

回答 18

在Python中是否有一个等效于float的range()?否使用此:

def f_range(start, end, step):
    a = range(int(start/0.01), int(end/0.01), int(step/0.01))
    var = []
    for item in a:
        var.append(item*0.01)
    return var

Is there a range() equivalent for floats in Python? NO Use this:

def f_range(start, end, step):
    a = range(int(start/0.01), int(end/0.01), int(step/0.01))
    var = []
    for item in a:
        var.append(item*0.01)
    return var

回答 19

这里有几个答案不能处理简单的极端情况,例如负步,错误的启动,停止等。这是正确处理许多情况的版本,具有与native相同的行为range()

def frange(start, stop=None, step=1):
  if stop is None:
    start, stop = 0, start
  steps = int((stop-start)/step)
  for i in range(steps):
    yield start
    start += step  

请注意,这将错误出step = 0,就像native一样range。区别之一是本机范围返回的对象是可索引的和可逆的,而上面的对象则不是。

您可以在此处使用此代码和测试用例。

There several answers here that don’t handle simple edge cases like negative step, wrong start, stop etc. Here’s the version that handles many of these cases correctly giving same behaviour as native range():

def frange(start, stop=None, step=1):
  if stop is None:
    start, stop = 0, start
  steps = int((stop-start)/step)
  for i in range(steps):
    yield start
    start += step  

Note that this would error out step=0 just like native range. One difference is that native range returns object that is indexable and reversible while above doesn’t.

You can play with this code and test cases here.


声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。