|I l@ve RuBoard|
1.16 Spanning a Range Defined by Floats
Credit: Dinu C. Gherman, Paul M. Winkler
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梩he 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.
1.16.4 See Also
Documentation for the range built-in function in the Library Reference; Numeric Python (http://www.pfdubois.com/numpy/).
|I l@ve RuBoard|