A rational proposal

Bengt Richter bokr at oz.net
Mon Dec 20 20:16:01 CET 2004

On Fri, 17 Dec 2004 21:29:52 -0600, Mike Meyer <mwm at mired.org> wrote:

>Title: A rational number module for Python
>Version: $Revision: 1.4 $
>Last-Modified: $Date: 2003/09/22 04:51:50 $
>Author: Mike Meyer <mwm at mired.org>
>Status: Draft
>Type: Staqndards
>Content-Type: text/x-rst
>Created: 16-Dec-2004
>Python-Version: 2.5
>Post-History: 30-Aug-2002
>This PEP proposes a rational number module to add to the Python
>standard library.
>Rationals are a standard mathematical concept, included in a variety
>of programming languages already.  Python, which comes with 'batteries
>included' should not be deficient in this area.  When the subject was
>brought up on comp.lang.python several people mentioned having
>implemented a rational number module, one person more than once. In
>fact, there is a rational number module distributed with Python as an
>example module.  Such repetition shows the need for such a class in the
>standard library.
>There are currently two PEPs dealing with rational numbers - 'Adding a
>Rational Type to Python' [#PEP-239] and 'Adding a Rational Literal to
>Python' [#PEP-240], both by Craig and Zadka.  This PEP competes with
>those PEPs, but does not change the Python language as those two PEPs
>do [#PEP-239-implicit]. As such, it should be easier for it to gain
>acceptance. At some future time, PEP's 239 and 240 may replace the
>``rational`` module.
>The module shall be ``rational``, and the class ``Rational``, to
>follow the example of the decimal [#PEP-327] module. The class
>creation method shall accept as arguments a numerator, and an optional
>denominator, which defaults to one.  Both the numerator and
>denominator - if present - must be of integer type.  Since all other
>numeric types in Python are immutable, Rational objects will be
>immutable.  Internally, the representation will insure that the
>numerator and denominator have a greatest common divisor of 1, and
>that the sign of the denominator is positive.
IMO a string should also be a legitimate constructor argument,
as e.g. it is for int. This also provides the opportunity to accept
strings ordinarily representing floating point values and convert them
to exact rationals. E.g., '1.23' is exactly 123/100 so IWT it convenient
if rational.rat('1.23') == rational.rat(123, 100).

This principle is easily extended to '1.23e-45' etc., since any similar
otherwise-floating-point literal string can be represented exactly as
a rational if integer values of numerator and denominator are not limited
-- which they aren't in Python. Decimal floating point literal notation is
quite handy, and it is easy and exact to convert to rational, though
the reverse in not possible in general. A small further extension to literal
representation is to allow two of the aforementioned style of literals
to be joined with a '/' so that rat('x/y') == rat('x')/rat('y')
-- i.e., you could implement this extension of literal format by just trying
a literal_string.split('/') and doing the obvious. This also creates
a possible general repr format that can represent any rational accurately,
and the possibility if desired of a "friendly" __str__ format where a denominator
can be made a power of 10, e.g. '0.6' where __repr__ would be '3/5'.

>The ``Rational`` class shall define all the standard mathematical
>operations: addition, subtraction, multiplication, division, modulo
>and power.  It will also provide the methods:
>- max(*args): return the largest of a list of numbers and self.
>- min(*args): return the smallest of a list of numbers and self.
>- decimal(): return the decimal approximation to the rational in the
>             current context.
>- inv(): Return the inverse of self.
>Rationals will mix with all other numeric types.  When combined with an
>integer type, that integer will be converted to a rational before the
>operation.  When combined with a floating type - either complex or
>float - the rational will be converted to a floating approximation
>before the operation, and a float or complex will be returned.  The
>reason for this is that floating point numbers - including complex -
>are already imprecise.  To convert them to rational would give an
>incorrect impression that the results of the operation are
>precise.  Decimals will be converted to rationals before the
>operation.  [Open question: is this the right thing to do?]
Sounds right, iff the decimal really does represent an exact value.
But after a division or multiplication of decimals, I'm not sure I'm
comfortable with calling those results exact in the same sense as
if the operations had been with rationals.

IMO the issue of exactness deserves particular emphasis. Otherwise
why even bother with a rational type? If a decimal represents an exact
value, then it should become an exact rational in operations with rationals.

If you define the result of some rounding operations as exact, then
you could make exact rationals from the results. E.g., the string literal
argument would let you take an inexact float
    f = 0.1
and round int with string formatting and convert thus
    r = rat('%.2f'%f)   # could also pass a rounding spec to rat, like rat(f, 2)

and similarly with a decimal type. So that's one mechanism to do capture
of exact values from values known to represent a set of exact values using a set
of approximations that cluster around the exact values in way that makes
unambiguous determination of the represented values possible. (I'm trying to
state it like a patent lawyer grabbing for unanticipated general coverage ;-)
>Rationals can be converted to floats by float(rational), and to
>integers by int(rational).
>The module will define and at times raise the following exceptions:
>- DivisionByZero: divide by zero
>- OverflowError: overflow attempting to convert to a float.
I wonder if forced conversion to float or complex from some operation shouldn't
be an exception too. The result could be passed in the exception object, so it
would be easy to wrap risky operations in try/except. Or is a test for
isinstance(x, (float, complex)) the way to go? Is inexactness an exception w.r.t rational?
>There is currently a rational module distributed with Python, and a
>second rational module in the Python cvs source tree that is not
>distributed.  While one of these could be chosen and made to conform
>to the specification, I am hoping that several people will volunteer
>implementatins so that a ''best of breed'' implementation may be
>.. [#PEP-239] Adding a Rational Type to Python, Craig, Zadka
>   (http://www.python.org/peps/pep-0239.html)
>.. [#PEP-240] Adding a Rational Literal to Python, Craig, Zadka
>   (http://www.python.org/peps/pep-0240.html)
>.. [#PEP-327] Decimal Data Type, Batista
>   (http://www.python.org/peps/pep-0327.html)
>.. [#PEP-239-implicit] PEP 240 adds a new literal type to Pytbon,
>                       PEP 239 implies that division of integers would
>                       change to return rationals.
>This document has been placed in the public domain.
>   Local Variables:
>   mode: indented-text
>   indent-tabs-mode: nil
>   sentence-end-double-space: t
>   fill-column: 70
>   End:

Bengt Richter

More information about the Python-list mailing list