Recipe 19.1. Writing a range-like Function with Float IncrementsCredit: Dinu Gherman, Paul Winkler, Stephen Levings ProblemYou need an arithmetic progression, like the built-in xrange but with float values (xrange works only on integers). SolutionAlthough float arithmetic progressions are not available as built-ins, it's easy to code a generator to supply them: import itertools def frange(start, end=None, inc=1.0): "An xrange-like generator which yields float values" # imitate range/xrange strange assignment of argument meanings if end is None: end = start + 0.0 # Ensure a float value for 'end' start = 0.0 assert inc # sanity check for i in itertools.count( ): next = start + i * inc if (inc>0.0 and next>=end) or (inc<0.0 and next<=end): break yield next DiscussionSadly missing in the Python Standard Library, the generator in this recipe lets you use arithmetic progressions, similarly to the built-in xrange but with float values. Many theoretical restrictions apply, but this generator is more useful in practice than in theory. People who work with floating-point numbers all the time tell many war stories about billion-dollar projects that failed because someone did not take into consideration the strange things that modern hardware can do, at times, when comparing floating-point numbers. But for pedestrian cases, simple approaches like this recipe generally work. This observation by no means implies that you can afford to ignore the fundamentals of numerical analysis, if your programs need to do anything at all with floating-point numbers! For example, in this recipe, we rely on a single multiplication and one addition to compute each item, to avoid accumulating error by repeated additions. Precision would suffer in a potentially dangerous way if we "simplified" the first statement in the loop body to something like: next += inc as might appear very tempting, were it not for those numerical analysis considerations. One variation you may want to consider is based on pre-computing the number of items that make up the bounded arithmetic progression: import math def frange1(start, end=None, inc=1.0): if end == None: end = start + 0.0 # Ensure a float value for 'end' start = 0.0 nitems = int(math.ceil((end-start)/inc)) for i in xrange(nitems): yield start + i * inc This frange1 version may or may not be faster than the frange version shown in the solution; if the speed of this particular generator is crucial to your programs, it's best to try both versions and measure resulting times. In my limited benchmarking, on most of the hardware I have at hand, frange1 does appear to be consistently faster. Talking about speedbelieve it or not, looping with for i in itertools.count( ) is measurably faster than apparently obvious lower-level alternatives such as: i = 0 while True: ...loop body unchanged... yield next i += 1 Do consider using itertools any time you want speed, and you may be in for more of these pleasant surprises. If you work with floating-point numbers, you should definitely take a look at Numeric and other third-party extension packages that make Python such a powerful language for floating-point computations. For example, with Numeric, you could code something like: import math, Numeric def frange2(start, end=None, inc=1.0, typecode=None): if end == None: end = start + 0.0 # Ensure a float value for 'end' start = 0.0 nitems = math.ceil((end-start)/inc) return Numeric.arange(nitems) * inc + start This one is definitely going to be faster than both frange and frange1 if you need to collect all of the progression's items into a sequence. See AlsoDocumentation for the xrange built-in function, and the itertools and math modules, in the Library Reference; Numeric Python (http://www.pfdubois.com/numpy/). |