[Python-ideas] Float range class

M.-A. Lemburg mal at egenix.com
Sat Jan 10 11:17:21 CET 2015

On 10.01.2015 09:37, Andrew Barnert wrote:
> On Jan 9, 2015, at 23:03, Andrew Barnert <abarnert at yahoo.com.dmarc.invalid> wrote:
>> On Jan 9, 2015, at 21:13, Guido van Rossum <guido at python.org> wrote:
>>> - numeric tricks to avoid intermediate overflows or numeric instabilities, plus optimizations
>> I'll leave this to others. (If no one steps up, I can look at the numpy code, but I'm sure this list has people who know it off the top of their heads.)
> Actually, here's what numpy does:
>     step = (stop-start)/float((num-1))
>     y = arange(0, num) * step + start
>     y[-1] = stop
> De-vectorize that, and it's exactly your too-naive algorithm (except for the off-by-1), with a simple fix for inexact stop.
> From a quick check, the timing is almost identical (6.89us vs. 6.49us to enumerate linspace(1, 2, 5000). 
> It doesn't _normally_ accumulate rounding errors. For example, whether I use the same test, or much smaller numbers, every 5th element, and sometimes the one immediately after, is off by one digit, but the intervening elements are identical. However, if you get down to denormal values, then the underflow _does_ accumulate. For example, linspace(0, 2e-323, 10) should give (0, 0, 5e-324, 5e-324. ...), and the smart version does that, but the naive version--and numpy--gives all 0s. And if you build a simple 4.2 fixed-point type and ask for linspace(0, .1, 100), again you get all 0s. On the other hand, the naive version avoids overflow problems at the other end of the scale, so maybe that's a wash?

Hmm, I guess that depends on what floats you "normally" use ;-)
You can't even represent 0.1 accurately in IEEE floats:


Since we're not after performance here (use numpy for that), I
think it's more important to get as accurate as possible and
not have the last interval be larger or smaller than the rest,
or having the interval points further right in the list be off
of the exact value by an increasing amount (with positive or
negative delta).

The version I posted does not have these problems. It also has
the usual fp rounding issues per interval value, but those errors
don't accumulate, since you're not using the same delta
across the whole range.

I'm sure it can be improved, but it's certainly a more accurate
approach than the naive "accumulate rounding errors and then fix
the end point" approach :-)

def naive_frange(start, stop, steps):
    start = float(start)
    stop = float(stop)
    steps = int(steps)
    delta = (stop - start) / steps
    for i in range(steps + 1):
        yield start + i * delta

def frange(start, stop, steps):
    start = float(start)
    stop = float(stop)
    steps = int(steps)
    for i in range(steps + 1):
        yield ((steps - i) * start + i * stop) / steps


naive_frange: [0.0, 0.1, 0.2, 0.30000000000000004, 0.4, 0.5, 0.6000000000000001, 0.7000000000000001,
0.8, 0.9, 1.0]

frange: [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]

Marc-Andre Lemburg

Professional Python Services directly from the Source  (#1, Jan 10 2015)
>>> Python Projects, Coaching and Consulting ...  http://www.egenix.com/
>>> mxODBC Plone/Zope Database Adapter ...       http://zope.egenix.com/
>>> mxODBC, mxDateTime, mxTextTools ...        http://python.egenix.com/

::::: Try our mxODBC.Connect Python Database Interface for free ! ::::::

   eGenix.com Software, Skills and Services GmbH  Pastor-Loeh-Str.48
    D-40764 Langenfeld, Germany. CEO Dipl.-Math. Marc-Andre Lemburg
           Registered at Amtsgericht Duesseldorf: HRB 46611

More information about the Python-ideas mailing list