update of vegasets qrange

TrustyTony 0 Tallied Votes 661 Views Share

Saw somebody was viewing this thread and thought the qrange needed one update. From post http://www.daniweb.com/code/snippet216627.html.

# qrange( [start,] stop[, step] ) is a generator, works similar to xrange()
def qrange(start, stop=None, step=1):
    """if start is missing it defaults to zero, somewhat tricky"""
    start, stop =  (0, start) if stop is None else  (start, stop)
    # allow for decrement
    while start > stop if step<0 else start < stop:
        yield start   # makes this a generator for new start value
        start += step

# test
print qrange(0, 10, 2)        # <generator object at 0x009D5E68>
print list(qrange(0, 10, 2))  # [0, 2, 4, 6, 8]
print list(qrange(10,0, -1))
print list(qrange(0,10,-1))

Dani AI

Generated

Good catch by — repeated addition of a floating-point step (for example 0.1) will accumulate rounding error and can produce the wrong number of elements. The small-epsilon tweak helps in many cases but is brittle: picking a single epsilon that works for all magnitudes and signs is tricky.

A more robust pattern is to compute the number of steps up front and yield each value as start + i*step (compute from the index), rather than repeatedly adding step. That avoids error build‑up because each output is a single multiply+add instead of an accumulated sum. The implementation below mirrors range semantics, checks for step == 0, and uses a tiny relative tolerance when converting the span to an integer count.

import math

def qrange(start, stop=None, step=1.0):
    if stop is None:
        start, stop = 0.0, float(start)
    else:
        start, stop = float(start), float(stop)
    step = float(step)
    if step == 0.0:
        raise ValueError("qrange() step must not be zero")
    forward = step > 0
    if (forward and start >= stop) or (not forward and start <= stop):
        return
    span = (stop - start) / step
    eps = 1e-12 * max(1.0, abs(span))
    n = max(0, int(math.ceil(span - eps)))
    for i in range(n):
        yield start + i * step

Notes: for exact decimal arithmetic prefer the decimal module (or fractions.Fraction) when base-10 precision matters; numpy.arange can show similar float-step issues, so use numpy.linspace when you need a fixed count of evenly spaced samples. This index-based approach complements ’s delta idea while avoiding cumulative error.

TrustyTony 888 ex-Moderator Team Colleague Featured Poster

This has problem of accumulating error during repeated summing and ends up for example giving 51 values for qrange(5, step=0.1). Would need to stop near the end not only after the end (-delta or +delta)

There is ready alternative as long as you have numpy: numpy.arange.

import numpy
# qrange( [start,] stop[, step] ) is a generator, works similar to xrange()
delta = 1e-6
def qrange(start, stop=None, step=1):
    """if start is missing it defaults to zero, somewhat tricky"""
    start, stop =  (0, start) if stop is None else  (start, stop)
    # allow for decrement
    while start > stop + delta if step<0 else start < stop - delta:
        yield start   # makes this a generator for new start value
        start += step

# test
print qrange(0, 10, 2)        # <generator object at 0x009D5E68>
print list(qrange(0, 10, 2))  # [0, 2, 4, 6, 8]
vals = list(qrange(5, step = 0.1))
vals2 = list(numpy.arange(0,5,0.1))
print 'qrange', len(vals)
print vals
print 'arange', len(vals2)
print vals2
Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.