Recipe 19.1. Writing a range-like Function with Float Increments
Credit: Dinu Gherman, Paul Winkler, Stephen
Levings
Problem
You need an arithmetic progression, like the built-in
xrange but with float values
(xrange works only on
integers).
Solution
Although 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
Discussion
Sadly 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 += incas 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 mathThis frange1 version may or may not be faster than
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
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 = 0Do consider using itertools any time you want
while True:
...loop body unchanged...
yield next
i += 1
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, NumericThis one is definitely going to be faster than
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
both frange and frange1 if you need
to collect all of the progression's items into a
sequence.
See Also
Documentation for the xrange built-in function,
and the itertools and math
modules, in the Library Reference; Numeric
Python (http://www.pfdubois.com/numpy/).