Welcome, guest | Sign In | My Account | Store | Cart

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.

Python, 20 lines
 ``` 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20``` ```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 ```

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. Paul Winkler 20 years, 1 month ago

This is faster. 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
`````` Stephen Levings 18 years, 9 months ago

A little simplification. count = int(math.ceil((end-start)/inc) so you don't need if start + count * inc != end: ... Chris Grebeldinger 16 years, 8 months ago

More Correct.

``````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 = start
for i in xrange(1,count):
L[i] = L[i-1] + inc
return L
`````` Walter Brunswick 16 years, 7 months ago

Even more correct, but not nearly complete... 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 = end
for i in (xrange(1,count)):
L[i] = L[i-1] + inc
return L
`````` Flávio Codeço Coelho 16 years, 7 months ago

Use Numeric. 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()
`````` Edvard Majakari 16 years, 6 months ago

More memory-efficient implementation with generators. 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 &lt; 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
`````` Eric-Olivier LE BIGOT 14 years, 9 months ago

Fast, flexible and memory-efficient; also accepts integers; does not require Numeric. 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))
`````` Eric-Olivier LE BIGOT 14 years, 9 months ago

This can break, unfortunately.

``````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. Eric-Olivier LE BIGOT 14 years, 9 months ago

range -> xrange, for memory efficiency. 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,...))". Eric-Olivier LE BIGOT 14 years, 9 months ago

Typo: int(ceil(...)/increment) -> int(ceil((...)/increment)). 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)
`````` Joel Miller 14 years, 5 months ago

Needs to test inputs. 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... Peter Williams 14 years, 3 months ago

short, no roundoff problems, fast. 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
`````` dwhall256 12 years, 7 months ago

A less naive generator-based version:

``````def frange6(*args):
"""A float range generator."""
start = 0.0
step = 1.0

l = len(args)
if l == 1:
end = args
elif l == 2:
start, end = args
elif l == 3:
start, end, step = args
if step == 0.0:
raise ValueError, "step must not be zero"
else:
raise TypeError, "frange expects 1-3 arguments, got %d" % l

v = start
while True:
if (step > 0 and v >= end) or (step < 0 and v <= end):
raise StopIteration
yield v
v += step
``````

Can be used like this:

``````>>> for i in frange6(42.):
...  pass
``````

or like this:

``````>>> l = list(frange6(42.))
`````` Nisan Haramati 8 years, 11 months ago

My versions use the original range function to create multiplicative indices for the shift. This allows same syntax to the original range function. I have made two versions, one using float, and one using Decimal, because I found that in some cases I wanted to avoid the roundoff drift introduced by the floating point arithmetic.

It is consistent with empty set results as in range/xrange.

Passing only a single numeric value to either function will return the standard range output to the integer ceiling value of the input parameter (so if you gave it 5.5, it would return range(6).)

``````## frange.py
from math import ceil
# find best range function available to version (2.7.x / 3.x.x)
try:
_xrange = xrange
except NameError:
_xrange = range

def frange(start, stop = None, step = 1):
"""frange generates a set of floating point values over the
range [start, stop) with step size step

frange([start,] stop [, step ])"""

if stop is None:
for x in _xrange(int(ceil(start))):
yield x
else:
# create a generator expression for the index values
indices = (i for i in _xrange(0, int((stop-start)/step)))
# yield results
for i in indices:
yield start + step*i

## drange.py
import decimal
from math import ceil
# find best range function available to version (2.7.x / 3.x.x)
try:
_xrange = xrange
except NameError:
_xrange = range

def drange(start, stop = None, step = 1, precision = None):
"""drange generates a set of Decimal values over the
range [start, stop) with step size step

drange([start,] stop, [step [,precision]])"""

if stop is None:
for x in _xrange(int(ceil(start))):
yield x
else:
# find precision
if precision is not None:
decimal.getcontext().prec = precision
# convert values to decimals
start = decimal.Decimal(start)
stop = decimal.Decimal(stop)
step = decimal.Decimal(step)
# create a generator expression for the index values
indices = (
i for i in _xrange(
0,
((stop-start)/step).to_integral_value()
)
)
# yield results
for i in indices:
yield float(start + step*i)

## testranges.py
import frange
import drange
list(frange.frange(0, 2, 0.5)) # [0.0, 0.5, 1.0, 1.5]
list(drange.drange(0, 2, 0.5, precision = 6)) # [0.0, 0.5, 1.0, 1.5]
list(frange.frange(3)) # [0, 1, 2]
list(frange.frange(3.5)) # [0, 1, 2, 3]
list(frange.frange(0,10, -1)) # []
`````` Created by Dinu Gherman on Tue, 7 Aug 2001 (PSF)

### Required Modules

• (none specified)