【问题标题】:range() for floatsrange() 用于浮点数
【发布时间】:2016-03-10 22:51:46
【问题描述】:

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

【问题讨论】:

  • 那些不是分数而是浮点数。并且浮点数......好吧,可能会给出与您预期不同的结果。
  • 一个快速的解决方法是将整数视为小数,例如:range(5, 50, 5),然后将每个数字除以 10。
  • @delnan - 已更新。为了方便浮动范围,我愿意接受微小的误差
  • @NullUserException - 这只是一个例子 - 真正的代码当然是参数化的:)

标签: python range fractions decimal


【解决方案1】:

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

【讨论】:

  • 相当糟糕的解决方案,试试f_range(0.01,0.02,0.001)... 对于大多数实际用途,来自 Numpy 的arange 是一个简单、安全和快速的解决方案。
  • 你是对的。使用 numpy 比我的代码快 1.8。
  • 你是对的。使用 numpy 比我的代码快 1.8。但是我工作的系统是完全封闭的。只有 Python 和 pyserial 没有了。
  • Numpy 不能安装在旧电脑上
【解决方案2】:

我不知道问题是否老了,但NumPy 库中有一个arange 函数,它可以作为一个范围工作。

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】:
    我不知道内置函数,但是写一个像 [this](https://stackoverflow.com/a/477610/623735) 这样的函数应该不会太复杂。
    def frange(x, y, jump):
      while x < y:
        yield x
        x += jump
    
    罢工> ---

    正如 cmets 所提到的,这可能会产生不可预测的结果,例如:

    >>> 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 和整数开始和停止(xy),这可以正常工作。如需更通用的解决方案,请参阅here。]

    【讨论】:

    • Python 的座右铭其实是There should be one-- and preferably only one --obvious way to do it。但无论如何 Python 都很棒 :)
    • &gt;&gt;&gt; print list(frange(0,100,0.1))[-1]==100.0 将是 False
    • frange 可能会意外工作。由于curse of floating point arithmetics,例如frange(0.0, 1.0, 0.1) 产生11 个值,其中最后一个值为0.9999999999999999。一个实际的改进是while x + sys.float_info.epsilon &lt; y:,尽管甚至是this can probably fail with large numbers
    • -1 请不要使用此代码,至少不要在可能影响我生活的软件中使用。没有办法让它可靠地工作。也不要使用 Akseli Palén 的答案。使用 Xaerxess 或 wim 的答案(忽略关于 arange 的部分除外)。
    • 如果你使用decimal.Decimal 作为步进而不是浮动,这很好用。
    【解决方案4】:

    solution without numpy etc dependencies 由 kichik 提供,但由于 the floating point arithmetics,它经常出现意外行为。正如meblubberdiblub 所指出的,其他元素很容易潜入结果中。例如,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
    

    代码也可通过a GitHub Gist 获得。

    【讨论】:

    • 这会因 frange(2.0, 17.0/6.0, 1.0/6.0) 而失败。它永远不可能变得健壮。
    • @benrg 感谢您指出这一点!它让我意识到 epsilon 应该依赖于跳转,所以我检查了算法并修复了这个问题。这个新版本更加强大,不是吗?
    • frange(0.026, 0.619, 0.078) 失败。
    • @smichr 感谢您的关注。我解决了这个问题。不知何故,我没有考虑 y - x 不是跳跃的倍数的情况。我怎么会这么瞎?好吧,解决方法是确保在产量之前 x 小于或等于 y。现在frange(0.026, 0.619, 0.078) 产生0.026, 0.104, 0.182, 0.26, 0.338, 0.416, 0.494, 0.572 应该是这样。
    • 我没有看到代码中的条件。另外,检查不应以 0.493 结尾的情况 frange(.071,.493,.001)。但是,如果您认为这是因为 x &lt;= y 时的发射,您可以将其更改为 x &lt; y 但随后 frange(0.569, 0.799, 0.23) 将失败,因为它发射超过 0.569。我正在针对我提供的版本测试代码。
    【解决方案5】:

    虽然基于整数的范围在“所见即所得”中得到了很好的定义,但有些东西在浮点数中不容易看到,这会导致在所需范围内获得看似明确定义的行为时遇到麻烦.

    可以采取两种方法:

    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)
    

    在作为答案提供的方法中,最好使用 Fraction here 以及将输入作为字符串处理的选项。我有一些建议可以让它变得更好:

    1. 让它处理类似范围的默认值,以便您可以自动从 0 开始
    2. 让它处理递减范围
    3. 使输出看起来像您使用精确算术时所期望的那样

    我提供了一个例程,它可以做同样的事情,但不使用 Fraction 对象。相反,它使用round 来创建具有与使用 python 打印数字相同的明显数字的数字,例如1 位小数表示 0.1 和 3 位小数表示 0.004:

    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)
    

    【讨论】:

      【解决方案6】:

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

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

      当我们考虑到索引的生成与索引的数量和它们的值一样多时,在标准库中使用 range() 的浮点实现就没有意义了。例如,如果我们调用函数 frange(0, 10, 0.25),我们会期望 0 和 10 都包含在内,但这会产生一个有 41 个值的生成器,而不是 10/0.25 可能期望的 40 个值。

      因此,根据其用途,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。

      【讨论】:

      • 这是一个相当扭曲的论点。 range() 应该简单地查看迭代生成器,它是否用于 for 循环或索引某些东西应该留给调用者。几千年来,人们一直在 for 循环中使用浮点数,以上理由都是荒谬的。 Python 委员会的人在这里搞砸了很多次,好的争论可能会被上面的一些扭曲的理由淹没。就是这么简单明了。现在有太多像上面这样的决定被 Python 语言所奉为。
      • 如果有任何要返回的点,函数的第一个值应该是起点; list(frange_S(2,3,4)) is [] but should be [2.0]
      【解决方案7】:

      谈论从鼹鼠山变成山。 如果您放宽对 range 函数的浮点模拟的要求,并且只需创建一个易于在 for 循环中使用的浮点列表,那么编码将变得简单而健壮。

      def super_range(first_value, last_value, number_steps):
          if not isinstance(number_steps, int):
              raise TypeError("The value of 'number_steps' is not an integer.")
          if number_steps < 1:
              raise ValueError("Your 'number_steps' is less than 1.")
      
          step_size = (last_value-first_value)/(number_steps-1)
      
          output_list = []
          for i in range(number_steps):
              output_list.append(first_value + step_size*i)
          return output_list
      
      first = 20.0
      last = -50.0
      steps = 5
      
      print(super_range(first, last, steps))
      

      输出将是

      [20.0, 2.5, -15.0, -32.5, -50.0]
      

      请注意,函数super_range 不限于浮点数。它可以处理定义了运算符+-*/的任何数据类型,例如complexDecimalnumpy.array

      import cmath
      first = complex(1,2)
      last = complex(5,6)
      steps = 5
      
      print(super_range(first, last, steps))
      
      from decimal import *
      first = Decimal(20)
      last = Decimal(-50)
      steps = 5
      
      print(super_range(first, last, steps))
      
      import numpy as np
      first = np.array([[1, 2],[3, 4]])
      last = np.array([[5, 6],[7, 8]])
      steps = 5
      
      print(super_range(first, last, steps))
      

      输出将是:

      [(1+2j), (2+3j), (3+4j), (4+5j), (5+6j)]
      [Decimal('20.0'), Decimal('2.5'), Decimal('-15.0'), Decimal('-32.5'), Decimal('-50.0')]
      [array([[1., 2.],[3., 4.]]),
       array([[2., 3.],[4., 5.]]),
       array([[3., 4.],[5., 6.]]),
       array([[4., 5.],[6., 7.]]),
       array([[5., 6.],[7., 8.]])]
      

      【讨论】:

        【解决方案8】:

        正如 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 将按预期工作。要使此功能在这种情况下工作,您必须应用类似于以下内容的 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
        

        通过这个 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]
        

        解决方案 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 章。可迭代对象、迭代器和生成器)。 它不适用于递减范围。您必须像之前的解决方案一样应用 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')]
        

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

        小心

        如果你想在负步数中使用这些功能,你应该添加一个检查步数符号,例如:

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

        我想,你说得对。你可以使用这些函数中的任何一个(除了第一个函数),all你需要的是 python 标准库。

        【讨论】:

          【解决方案9】:

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

          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]
          

          【讨论】:

            【解决方案10】:

            用法

            # 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 已弃用?
            【解决方案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: 看了几篇这里的讨论,但是现在第三次回到这个问题,我觉得应该把这个信息放在更易读的位置。

            【讨论】:

              【解决方案12】:

              这里有几个答案不能处理简单的边缘情况,如负步、错误开始、停止等。这是正确处理许多这些情况的版本,提供与本机 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 错误,就像原生 range 一样。一个区别是原生范围返回的对象是可索引和可​​逆的,而上面不是。

              您可以在这里play with this code 和测试用例。

              【讨论】:

                【解决方案13】:

                更简单的无库版本

                哎呀,我会折腾一个简单的无库版本。随意改进它[*]:

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

                【讨论】:

                • 如果你想在输出中包含停止值,通过添加停止+跳转,这个方法然后恢复到中间有错误浮点的幼稚结果,试试frange(0,1.1,0.1)甚至更多那些选择像frange(0,1.05,0.1)
                • @alancalvitti:你对“坏”浮点的定义是什么?是的,结果可能无法很好地打印出来,但 frange() 在浮点表示的限制范围内提供了最接近的一组均匀间隔的值。你会如何改进它?
                • 好点,我已经习惯了高级语言,在这种语言中你会为这样的任务使用有理数,Py 感觉就像汇编。
                • 组装?哼哼! ;) 当然 Python 可以提供分数的精确表示:docs.python.org/3/library/fractions.html
                • 好的,谢谢,但是例如,我喜欢的语言会自动转换这些类型,所以 1/2 是有理数,而 1/2.0 是浮点数,不需要这样声明 - 离开对 Java 的声明,它比 Py 甚至更低/汇编。
                【解决方案14】:

                我认为有一个非常简单的答案可以真正模拟范围的所有功能,但对于浮点数和整数都是如此。在此解决方案中,您只需假设默认情况下的近似值为 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)
                

                【讨论】:

                  【解决方案15】:

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

                  【讨论】:

                    【解决方案16】:
                    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]。

                    【讨论】:

                      【解决方案17】:

                      没有这样的内置函数,但您可以使用以下(Python 3 代码)尽可能安全地完成这项工作。

                      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 上提供的代码

                      【讨论】:

                        【解决方案18】:

                        您可以使用:

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

                        或使用 lambda / map:

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

                        【讨论】:

                        • 和 array(range(5,50,15)) / 10.0 作为 numpy 数组有处理除法,乘法等操作符
                        • @edvaldig:你说得对,我不知道这个...不过我认为arange(0.5, 5, 1.5) 更易读于 IMO。
                        • 我更喜欢这个答案而不是接受的答案,因为前两个解决方案是基于迭代整数并从整数中推导出最终的浮点数。这更健壮。如果您直接使用浮点数执行此操作,由于浮点数在内部的表示方式,您可能会遇到奇怪的一次性错误。例如,如果您尝试list(frange(0, 1, 0.5)),它可以正常工作并排除 1,但如果您尝试list(frange(0, 1, 0.1)),您得到的最后一个值接近 1.0,这可能不是您想要的。这里介绍的解决方案没有这个问题。
                        • 永远不要使用 numpy.arange (numpy 文档本身建议不要使用它)。使用 wim 推荐的 numpy.linspace 或此答案中的其他建议之一。
                        【解决方案19】:

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

                        【讨论】:

                        • +1 用于使用itertools.takewhile。但是,itertools.count(start, step) 存在累积的浮点错误。 (例如评估takewhile(lambda x: x &lt; 100, count(0, 0.1))。)我会改写takewhile(lambda x: x &lt; stop, (start + i * step for i in count()))
                        【解决方案20】:

                        我写了一个函数,它返回一个包含双精度浮点数范围的元组,没有任何超过百分之一的小数位。这只是解析范围值(如字符串)并分离多余部分的问题。我用它来显示范围以从 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
                        

                        【讨论】:

                          【解决方案21】:

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

                          【讨论】:

                          • 自 matplotlib 2.2 版起已弃用 Frange。应该使用 numpy.arange。
                          【解决方案22】:

                          我曾经使用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]
                          • @TNT 不,这不是错误。你会发现np.linspace(-.1,10,num=5050)[0] == -.1 是真的。只是repr(np.float64('-0.1'))显示的位数更多。
                          • 虽然该特定示例没有显示过多的舍入误差,但也有失败的情况。例如,当理想结果为1.0 时,print(numpy.linspace(0, 3, 148)[49]) 将打印0.9999999999999999linspacearange 做得更好,但不能保证产生最小可能的舍入误差。
                          • 保证执行正确的端点处理,并且总是准确地产生请求数量的元素。
                          【解决方案23】:

                          热切评估 (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.
                          

                          【讨论】:

                          • +1;但为什么不将(x * .5 for x in range(10)) 作为惰性求值的生成器表达式呢?
                          • 因为那太容易了,我猜? :)
                          猜你喜欢
                          • 1970-01-01
                          • 2012-09-06
                          • 1970-01-01
                          • 2022-12-19
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          • 2021-06-26
                          • 2012-11-18
                          相关资源
                          最近更新 更多