[Edu-sig] Re: Still working on rational numbers PEP?

Kirby Urner urnerk@qwest.net
Mon, 30 Sep 2002 12:14:20 -0700


============
"Christopher A. Craig" <ccraig@ccraig.org> wrote:

I think your problem is in assuming that Python 3 division will return
a float. The plan for Python 3 is that the / operator will return "a
reasonable approximation to the mathematical result of division."[1] The
reason it is presently slated to return a float is that there is no
rational in Python. Guido has even stated [2]

   By the time 3.0 comes around, (1L<<2000)/1 will probably return a
   rational number. But until we have added rationals, I think that for
   most apps, long/long -> float is the only definition that is
   consistent with the other requirements...

So if rationals go in before 3.0, 1/2 may be the rational one-half and
not the float 0.5. Changing the behaviour of the divide operator is
going to break a bunch of code regardless of whether the new code
returns a float or a rational, but that breakage is already scheduled
to happen.

 > >>> a = 1r2 + 1r2
 > >>> a
 > 1
 > >>> 1/a
 > 1
 > >>> a/3
 > 1r3
 >
 > If you have the time, please show me how you would express the
 > above using your proposed extensions.
 >
 >>> 1/2
1/2
 >>> a = 1/2 + 1/2
 >>> a
1
 >>> 1/a
1
 >>> a/3
1/3

>====================================================================

Kirby:

I think there's an option to be less radical and break less pre-
existing code.  According to the above scenario, code rewritten to
accommodate 1/2 == 0.5 as per recent changes, will have to be
rewritten *again*.

I would prefer to see / return a rational if and only both
arguments are rational.  Otherwise it should return a float,
as per the current plan.

Rational arithmetic is in general rather expensive, and the
expectation the 4/17 will take us into floating point territory
is entirely reasonable I think.  Only if 4 and 17 are both
type rational would 4/17 then not return a type float answer.

So I would prefer we keep the option to go:

 >>> 5e_1
0.5
 >>> 1r2
1r2

on the table.  Maybe I will subscribe to python-dev and try to
at least prompt a little worthwhile discussion of the proposal,
recognizing that it may well be dismissed.  Or you could do
a quick blurb on "what Kirby thinks".  In the meantime, I'm
CC-ing this to edu-sig, where this thread got started (for me).
I thank you for you patience in bringing me up to speed on the
current proposal.

Given we're in the initial stages of this and a lot of Pythonistas
may not be aware of the J language option, which is quite viable,
I think it should at least be looked at.

In J, the division operator is % (because / means something else).

So in J, a conversation might go like this:

    1%2  NB.  i.e. 1/2 -- same as Python 2.3
0.5
    1r2
1r2
    1r2 + 1r3
5r6
    1%2 + 1%3
0.428571

The last line above is a surprise -- because J (unlike Python)
has no concept of operator precedence (Python should stay the
way it is of course).  1%2 + 1%3 really means (in Python)
1/(2 + (1/3)).  To get more what we'd expect, we go:

    (1%2) + (1%3)
0.833333
   (x:0.5555) % 1r2  NB.  x: coerces 0.5555 to be rational
1111r1000

In Python, we might go:

   >>> rat(0.5555)
1111r2000
   >>> rat(0.5555)/1r2
1111r1000

The main objection to my proposal, I think, is that it seems
to return us to the bad old days (like now), when / can do
different things depending on the types of the operands.  But
of course that's true with many operands.  float + float is
float, int + int is int.  The objection, specifically in the
case of division, is you "lose information" by returning 0 in
place of 0.5 (depending on whether this is int/int or float/float)
-- these are NOT "reasonably equivalent" answers.

In contrast, returning a rational type would be to return the
*fractional equivalent* of a float, i.e. the results are arguably
the same, within tolerance.  Just as 1.0 == 1, so 1r2 == 0.5 == 5e_1.
so no information is lost, perhaps conforming to Guido's long term
intentions for / (of course I don't speak for Guido in any way
-- maybe I'm way off base here).

In J, when you coerce pi to a rational, you get:
1285290289249r409120605684 which in floating point is
3.1415926535896439.  That's not *quite* the same as

 >>> math.pi
3.1415926535897931

but in Python we'd be free to go to more decimal places using
longs in both numerator and denominator -- if we want (could do
that in J to -- I'll have to ask why they didn't).

Kirby