[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