Credit: Dinu C. Gherman, Paul M. Winkler
You need an arithmetic progression, just like
the built-in function range
, but with float values
(range
works only on integers).
Although this functionality is not available as a built-in, it’s not hard to code it with a loop:
def frange(start, end=None, inc=1.0): "A range-like function that does accept float increments..." if end == None: end = start + 0.0 # Ensure a float value for 'end' start = 0.0 assert inc # sanity check 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
Sadly missing in the Python standard library, the function in this
recipe lets you use ranges, just as with the built-in function
range
, but with float arguments.
Many theoretical restrictions apply, but this function is more useful in practice than in theory. People who work with floating-point numbers all the time have many war stories about billion-dollar projects that failed because someone did not take into consideration the strange things that modern hardware does when comparing floating-point numbers. But for pedestrian cases, simple approaches like this recipe generally work.
You can get a substantial speed boost by preallocating the list
instead of calling append
repeatedly. This also
allows you to get rid of the conditionals in the inner loop. For one
element, this version is barely faster, but with more than 10
elements it’s consistently about 5 times
faster—the kind of performance ratio that is worth caring
about. I get identical output for every test case I can think of:
def frange2(start, end=None, inc=1.0): "A faster range-like 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 count = int((end - start) / inc) if start + count * inc != end: # Need to adjust the count. AFAICT, it always comes up one short. count += 1 L = [start] * count for i in xrange(1, count): L[i] = start + i * inc return L
Both versions rely on a single multiplication and one addition to
compute each item, to avoid accumulating error by repeated additions.
This is why, for example, the body of the for
loop
in frange2
is not:
L[i] = L[i-1] + inc
In Python 2.2, if all you need to do is loop on the result of
frange
, you can save some memory by turning this
function into a simple generator, yielding an iterator when you call
it:
from _ _future_ _ import generators def frangei(start, end=None, inc=1.0): "An xrange-like simple generator that does accept float increments..." if end == None: end = start + 0.0 start = 0.0 assert inc # sanity check i = 0 while 1: next = start + i * inc if inc > 0 and next >= end: break elif inc < 0 and next <= end: break yield next i += 1
If you use this recipe a lot, you should probably take a look at Numeric Python and other third-party packages that take computing with floating-point numbers seriously. This recipe, for example, will not scale well to very large ranges, while those defined in Numeric Python will.
Documentation for the range
built-in function in
the Library Reference; Numeric Python
(http://www.pfdubois.com/numpy/).