range()函数是否适用于浮点数?

214

在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

2
这些不是分数,而是浮点数。浮点数可能会产生与您预期不同的结果。 - user395760
8
一个快速的解决方法是将整数视为小数,例如:range(5, 50, 5),然后只需将每个数字除以10。 - NullUserException
@delnan - 已更新。为了拥有浮点范围的便利,我愿意接受微小的不准确性。 - Jonathan Livni
2
可能是Python decimal range() step value的重复问题。 - Jonathan Livni
你可以从Python decimal range() step value问题以及它所涉及的frange(), a range function with float increments (ActiveState Code)中找到更多关于这个问题的好坏答案的深入见解。 - nealmcb
显示剩余4条评论
24个回答

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

你可以通过运行一些断言来验证所有内容:
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上获取。


3

我不知道这个问题是否已经过时,但是在NumPy库中有一个arange函数,它可以像range一样工作。

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

3

一个更简单的无库版本

哎呀,我来分享一个更简单的无库版本。欢迎大家进行改进[*]:

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() 返回的是一个列表,而不是生成器。但它满足了我的需求。

如果您想在输出中包含停止值,可以通过添加stop+jump来实现。但是这种方法会导致结果不准确,存在浮点数问题。您可以尝试使用frange(0,1.1,0.1)或者frange(0,1.05,0.1)等选择。 - alancalvitti
@alancalvitti:你对“坏”的浮点数有什么定义?是的,结果可能无法很好地打印出来,但frange()提供了在浮点表示限制范围内最接近的一组均匀间隔值。你会如何改进它? - fearless_fool
好观点,我太习惯使用高级语言,在这样的任务中会涉及到有理数范围,所以 Py 就像汇编语言一样。 - alancalvitti
汇编?哼!;) 当然,Python可以使用分数提供精确的表示:https://docs.python.org/3/library/fractions.html - fearless_fool
好的,谢谢。但是比如说,我喜欢的语言会自动转换这些类型,所以1/2是有理数,而1/2.0是浮点数,不需要声明它们的类型 - 把声明留给Java吧,它比Py还要低级/汇编。 - alancalvitti

3
可以使用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文档本身也建议不使用)。推荐使用numpy.linspace,或者在此答案中提到的其他建议之一。

注2:我已经阅读了这里的一些评论讨论,但是现在已经第三次回来这个问题了,我觉得这些信息应该放在一个更易读的位置。


2

使用方法

# 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的其他答案已被弃用。

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


你为什么认为np.arange已经被弃用了? - wim

1
我写了一个函数,返回一个双精度浮点数范围的元组,小数点后只保留百分位。这只是将范围值解析为字符串并拆分多余部分的简单操作。我在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

1

整数范围非常明确定义,即“所见即所得”,但在浮点数中有些东西不容易看到,导致在所需范围内获得看似明确定义的行为时会遇到麻烦。

有两种方法可以采用:

  1. 将给定范围分成一定数量的段:采用 linspace 方法,当您选择一个不能很好地划分跨度的点数时(例如,以7个步骤从0到1),您将接受大量小数位数,首步值为0.14285714285714285

  2. 提供您已知应起作用并希望其起作用的 WYSIWIG 步长。通常情况下,您的希望将破灭,因为获取的值会错过您想要到达的端点。

倍数可能高于或低于您的预期:

>>> 3*.1 > .3  # 0.30000000000000004
True

>>> 3*.3 < 0.9  # 0.8999999999999999
True

你将尝试通过添加步长的倍数而不是递增来避免累积误差,但问题总会出现,如果你在纸上手工进行计算,使用精确的小数时,你将无法得到预期结果。但你 知道 这应该是可能的,因为Python显示给你的是0.1而不是底层整数比例接近0.1的近似值:
>>> (3*.1).as_integer_ratio()
(1351079888211149, 4503599627370496)

在提供的答案方法中,使用带有“将输入处理为字符串选项”的分数here最好。我有一些建议可以使其更好:
  1. 使其处理类似范围的默认值,以便您可以自动从0开始
  2. 使其处理递减范围
  3. 使输出看起来像您使用精确算术时所期望的那样
我提供了一个例程,它做了同样的事情,但不使用Fraction对象。相反,它使用round创建具有与数字相同外观数字的数字,如果您使用python打印它们,例如对于0.1之类的东西,需要1个小数,对于0.004之类的东西,则需要3个小数:
def frange(start, stop, step, n=None):
    """return a WYSIWYG series of float values that mimic range behavior
    by excluding the end point and not printing extraneous digits beyond
    the precision of the input numbers (controlled by n and automatically
    detected based on the string representation of the numbers passed).

    EXAMPLES
    ========

    non-WYSIWYS simple list-comprehension

    >>> [.11 + i*.1 for i in range(3)]
    [0.11, 0.21000000000000002, 0.31]

    WYSIWYG result for increasing sequence

    >>> list(frange(0.11, .33, .1))
    [0.11, 0.21, 0.31]

    and decreasing sequences

    >>> list(frange(.345, .1, -.1))
    [0.345, 0.245, 0.145]

    To hit the end point for a sequence that is divisibe by
    the step size, make the end point a little bigger by
    adding half the step size:

    >>> dx = .2
    >>> list(frange(0, 1 + dx/2, dx))
    [0.0, 0.2, 0.4, 0.6, 0.8, 1.0]

    """
    if step == 0:
        raise ValueError('step must not be 0')
    # how many decimal places are showing?
    if n is None:
        n = max([0 if '.' not in str(i) else len(str(i).split('.')[1])
                for i in (start, stop, step)])
    if step*(stop - start) > 0:  # a non-null incr/decr range
        if step < 0:
            for i in frange(-start, -stop, -step, n):
                yield -i
        else:
            steps = round((stop - start)/step)
            while round(step*steps + start, n) < stop:
                steps += 1
            for i in range(steps):
                yield round(start + i*step, n)

0

我认为有一个非常简单的答案,可以真正模拟范围的所有特性,包括浮点数和整数。在这个解决方案中,您只需假设默认情况下的近似值为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)

0
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的名称。 "Range"函数的行为与您想要的完全一样。您可以在这里查看它的手册[http://reference.wolfram.com/language/ref/Range.html]。

0
Python中是否有适用于浮点数的range()等效函数? 没有。 使用这个:
def f_range(start, end, step, coef=0.01):
    a = range(int(start/coef), int(end/coef), int(step/coef))
    var = []
    for item in a:
        var.append(item*coef)
    return var

4
不太好的解决方案,请尝试使用f_range(0.01,0.02,0.001)......对于大多数实际目的,来自Numpy的arange是一种简单、安全且快速的解决方案。 - Bart
你是对的。使用numpy比我的代码快1.8倍。 - Grigor Kolev
你说得对。使用numpy比我的代码快1.8倍。但我工作的系统是完全封闭的,只能使用Python和pyserial,没有其他的了。 - Grigor Kolev
旧电脑上未安装Numpy。 - Grigor Kolev

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接