[Python-Dev] range objects in 3.x

Terry Reedy tjreedy at udel.edu
Tue Sep 27 21:43:26 CEST 2011


On 9/27/2011 1:03 PM, Guido van Rossum wrote:

> mimicking the behavior of range() for floats is a bad idea, and that
> we need to come up with a name for an API that takes start/stop/count
> arguments instead of start/stop/step.

[In the following, I use count as the number of intervals; the number of 
points is 1 more.]

I agree with others that we should not just have a floatrange. An 
exact-as-possible floatrange is trivially based on exact computations 
with fractions:

def floatrange(a, b, n):
     '''Yield floats a, b, and n-1 equally spaced floats in between.'''
     for num,dem in fracrange(a.as_integer_ratio(), 
b.as_integer_ratio(), n):
         yield num/dem

There are good reasons to expose the latter. If fracrange is done with 
the Fraction class, each ratio will be reduced to lowest terms, which 
means that the denominator will vary for each pair. In some situations, 
one might prefer a constant denominator across the series. Once a 
constant denominator is calculated (eash though not trivial), fracrange 
is trivially based on range. The following makes the denominator as 
small as possible if the inputs are in lowest terms:

def fracrange(frac1, frac2, n):
     '''Yield fractions frac1, frac2 and n-1 equally spaced fractions in 
between.
     Fractions are represented as (numerator, denominator > 0)  pairs.
     For output, use the smallest common denominator of the inputs
     that makes the numerator range an even multiple of n.
     '''
     n1, d1 = frac1
     n2, d2 = frac2
     dem = d1 * d2 // gcd(d1, d2)
     start = n1 * (dem // d1)
     stop = n2 * (dem // d2)
     rang = stop - start
     q, r = divmod(rang, n)
     if r:
         gcd_r_n = gcd(r, n)
         m =  n // gcd_r_n
         dem *= m
         start *= m
         stop *= m
         step  = rang // gcd_r_n  # rang * m // n
     else:
         step = q   # if r==0: gcd(r,n)==n, m==1, rang//n == q
     for num in range(start, stop+1, step):
         yield num,dem

Two example uses:
for i,j in fracrange((1,10), (22,10), 7): print(i,j)
print()
for i,j in fracrange((1,5), (1,1), 6): print(i,j)
## print

1 10
4 10
7 10
10 10
13 10
16 10
19 10
22 10

3 15
5 15
7 15
9 15
11 15
13 15
15 15

If nothing else, the above is easy to check for correctness ;-).
Note that for fraction output, one will normally want to be able to 
enter an explicit pair such as (1,5) or even (2,10) The decimal 
equivalent, .2, after conversion to float, gets converted by 
.as_integer_ratio() back to  (3602879701896397, 18014398509481984).

-- 
Terry Jan Reedy



More information about the Python-Dev mailing list