ASPN ActiveState Programmer Network  
ActiveState, a division of Sophos
/ Home / Perl / PHP / Python / Tcl / XSLT /
/ Safari / My ASPN /
Cookbooks | Documentation | Mailing Lists | Modules | News Feeds | Products | User Groups
Submit Recipe
My Recipes

All Recipes
All Cookbooks


View by Category

Title: frange(), a range function with float increments
Submitter: Dinu Gherman (other recipes)
Last Updated: 2001/08/07
Version no: 1.0
Category: Shortcuts

 

4 stars 3 vote(s)


Description:

Sadly missing in the Python standard library, this function
allows to use ranges, just as the built-in function range(),
but with float arguments.

All thoretic restrictions apply, but in practice this is
more useful than in theory.

Source: Text Source

def frange(start, end=None, inc=None):
    "A range function, that does accept float increments..."

    if end == None:
        end = start + 0.0
        start = 0.0

    if inc == None:
        inc = 1.0

    L = []
    while 1:
        next = start + len(L) * inc
        if inc > 0 and next >= end:
            break
        elif inc < 0 and next <= end:
            break
        L.append(next)
        
    return L

Discussion:

Despite all rhetoric considerations about rounding effects, this
was useful to my several times, so I guess it might be interesting
for others as well.



Add comment

Number of comments: 12

This is faster, Paul Winkler,Paul Winkler, 2001/10/13
You can get a substantial speed boost by pre-allocating the list instead of calling append over and over. This also allows you to get rid of the conditionals in the inner loop. For 1 element, this version is barely faster, and above about 10 elements it's consistently about 5 times faster. I get identical output for every test case I can think of.

def frange2(start, end=None, inc=None):
    "A range function, that does accept float increments..."

    if end == None:
        end = start + 0.0
        start = 0.0
    else: start += 0.0 # force it to be a float

    if inc == None:
        inc = 1.0

    count = int((end - start) / inc)
    if start + count * inc != end:
        # need to adjust the count.
        # AFAIKT, it always comes up one short.
        count += 1

    L = [None,] * count
    for i in xrange(count):
        L[i] = start + i * inc

    return L

Add comment

A little simplification, Stephen Levings, 2003/03/04
count = int(math.ceil((end-start)/inc) so you don't need if start + count * inc != end: ...
Add comment

More Correct, Chris Grebeldinger, 2005/04/07

The algorithm has a slight problem where floating point representation
error accumulates over the range, giving unexpected results:

[i/10. for i in range(-2,2)] == frange2(-0.2,0.2,0.1) -> False

Since 0.1 is actually 0.10000000001

This slight modification corrects the problem:

def frange3(start, end=None, inc=None):
    """A range function, that does accept float increments..."""
    import math

    if end == None:
        end = start + 0.0
        start = 0.0
    else: start += 0.0 # force it to be a float

    if inc == None:
        inc = 1.0
    count = int(math.ceil((end - start) / inc))

    L = [None,] * count

    L[0] = start
    for i in xrange(1,count):
        L[i] = L[i-1] + inc
    return L

Add comment

Even more correct, but not nearly complete..., Walter Brunswick, 2005/04/13
The 'start' and 'end' arguments in the previous scripts are out of place: the function initially starts at 0, and stop at 'end', 'end' itself exclusive, not the other way around. Suggestion: Allow a precision to be specified. (Not implemented yet.)

def frange4(end,start=0,inc=0,precision=1):
    """A range function that accepts float increments."""
    import math

    if not start:
        start = end + 0.0
        end = 0.0
    else: end += 0.0

    if not inc:
        inc = 1.0
    count = int(math.ceil((start - end) / inc))

    L = [None] * count

    L[0] = end
    for i in (xrange(1,count)):
        L[i] = L[i-1] + inc
    return L

Add comment

This can break, unfortunately, Eric-Olivier LE BIGOT, 2007/03/08

frange4(-1, 0, 0.1)
breaks the above frange4. A possible solution would be to set start = None as a default argument and test whether start is None.
Add comment

Use Numeric , Flávio Codeço Coelho, 2005/05/11
I think this is best solved by

from Numeric import *
arange(-1,1,0.1)
then if you really need a list:
arange(-1,1,0.1).tolist()

Add comment

More memory-efficient implementation with generators, Edvard Majakari, 2005/05/18
A naive but working xrange() -like implementation using generators could be as follows:

def xfrange(start, stop=None, step=None):
    """Like range(), but returns list of floats instead

    All numbers are generated on-demand using generators
    """

    if stop is None:
        stop = float(start)
        start = 0.0

    if step is None:
        step = 1.0

    cur = float(start)

    while cur < stop:
        yield cur
        cur += step
Usage:
if __name__ == '__main__':

    for f in xfrange(5): print f,
    print
    for f in xfrange(1, 3): print f,
    print
    for f in xfrange(1, 2, 0.25): print f,
    print

Add comment

Fast, flexible and memory-efficient; also accepts integers; does not require Numeric, Eric-Olivier LE BIGOT, 2007/03/08
The following implementation does not require Numeric and is fast, as the generator is created directly by python. It also accepts integers. There is no accumulation of errors, as the increment is not added incrementally.

import math
def frange5(limit1, limit2 = None, increment = 1.):
  """
  Range function that accepts floats (and integers).

  Usage:
  frange(-2, 2, 0.1)
  frange(10)
  frange(10, increment = 0.5)

  The returned value is an iterator.  Use list(frange) for a list.
  """

  if limit2 is None:
    limit2, limit1 = limit1, 0.
  else:
    limit1 = float(limit1)

  count = int(math.ceil(limit2 - limit1)/increment)
  return (limit1 + n*increment for n in range(count))

Add comment

range -> xrange, for memory efficiency, Eric-Olivier LE BIGOT, 2007/03/09
In the example above, the range function should really be replaced the xrange function, if memory efficiency is desired.
Precision: in the doc string, "list(frange)" means "list(frange(start,...))".
Add comment

Typo: int(ceil(...)/increment) -> int(ceil((...)/increment)), Eric-Olivier LE BIGOT, 2007/03/09
There is a small typo in the original code, which should be corrected as:

int(ceil(...)/increment) -> int(ceil((...)/increment))

Also, it is possible to mimic the behavior of the built-in range even better: frange(0,5,-1) should return an empty list. This can be accomplished with:
range(count) -> range(0,count)

Add comment

Needs to test inputs, Joel Miller, 2007/07/05
This should test the types of the input. If it's accidentally called with string inputs (e.g., "0.01", "1", "0.01") it will continue appending to the list until the machine runs out of memory. Not that I have any experience of this...
Add comment

short, no roundoff problems, fast, Peter Williams, 2007/08/28
The following algorithm is short, fast, and immune to roundoff errors. It only has one float divide, and it treats start and stop values on equal footing. The downside (I guess) is that it takes the number of points as the third argument, not the step size. Of course, you can modify it to take step size if you really want.

def myfrange(start, stop, n):
    L = [0.0] * n
    nm1 = n - 1
    nm1inv = 1.0 / nm1
    for i in range(n):
        L[i] = nm1inv * (start*(nm1 - i) + stop*i)
    return L

Add comment



Highest rated recipes:

1. A simple XML-RPC server

2. Web service accessible ...

3. Treat the Win32 Registry ...

4. Watching a directory ...

5. Union Find data structure

6. Function Decorators by ...

7. MS SQL Server log monitor

8. Table objects with ...

9. wx twisted support using ...

10. More accurate sum




Privacy Policy | Email Opt-out | Feedback | Syndication
© 2006 ActiveState Software Inc. All rights reserved.