Commit 253bd1f2 authored by Jeffrey Yasskin's avatar Jeffrey Yasskin

Add rational.Rational as an implementation of numbers.Rational with infinite

precision. This has been discussed at http://bugs.python.org/issue1682. It's
useful primarily for teaching, but it also demonstrates how to implement a
member of the numeric tower, including fallbacks for mixed-mode arithmetic.

I expect to write a couple more patches in this area:
 * Rational.from_decimal()
 * Rational.trim/approximate() (maybe with different names)
 * Maybe remove the parentheses from Rational.__str__()
 * Maybe rename one of the Rational classes
 * Maybe make Rational('3/2') work.
parent 1c2609ee
'''\
This module implements rational numbers.
The entry point of this module is the function
rat(numerator, denominator)
If either numerator or denominator is of an integral or rational type,
the result is a rational number, else, the result is the simplest of
the types float and complex which can hold numerator/denominator.
If denominator is omitted, it defaults to 1.
Rational numbers can be used in calculations with any other numeric
type. The result of the calculation will be rational if possible.
There is also a test function with calling sequence
test()
The documentation string of the test function contains the expected
output.
'''
# Contributed by Sjoerd Mullender
from types import *
def gcd(a, b):
'''Calculate the Greatest Common Divisor.'''
while b:
a, b = b, a%b
return a
def rat(num, den = 1):
# must check complex before float
if isinstance(num, complex) or isinstance(den, complex):
# numerator or denominator is complex: return a complex
return complex(num) / complex(den)
if isinstance(num, float) or isinstance(den, float):
# numerator or denominator is float: return a float
return float(num) / float(den)
# otherwise return a rational
return Rat(num, den)
class Rat:
'''This class implements rational numbers.'''
def __init__(self, num, den = 1):
if den == 0:
raise ZeroDivisionError, 'rat(x, 0)'
# normalize
# must check complex before float
if (isinstance(num, complex) or
isinstance(den, complex)):
# numerator or denominator is complex:
# normalized form has denominator == 1+0j
self.__num = complex(num) / complex(den)
self.__den = complex(1)
return
if isinstance(num, float) or isinstance(den, float):
# numerator or denominator is float:
# normalized form has denominator == 1.0
self.__num = float(num) / float(den)
self.__den = 1.0
return
if (isinstance(num, self.__class__) or
isinstance(den, self.__class__)):
# numerator or denominator is rational
new = num / den
if not isinstance(new, self.__class__):
self.__num = new
if isinstance(new, complex):
self.__den = complex(1)
else:
self.__den = 1.0
else:
self.__num = new.__num
self.__den = new.__den
else:
# make sure numerator and denominator don't
# have common factors
# this also makes sure that denominator > 0
g = gcd(num, den)
self.__num = num / g
self.__den = den / g
# try making numerator and denominator of IntType if they fit
try:
numi = int(self.__num)
deni = int(self.__den)
except (OverflowError, TypeError):
pass
else:
if self.__num == numi and self.__den == deni:
self.__num = numi
self.__den = deni
def __repr__(self):
return 'Rat(%s,%s)' % (self.__num, self.__den)
def __str__(self):
if self.__den == 1:
return str(self.__num)
else:
return '(%s/%s)' % (str(self.__num), str(self.__den))
# a + b
def __add__(a, b):
try:
return rat(a.__num * b.__den + b.__num * a.__den,
a.__den * b.__den)
except OverflowError:
return rat(long(a.__num) * long(b.__den) +
long(b.__num) * long(a.__den),
long(a.__den) * long(b.__den))
def __radd__(b, a):
return Rat(a) + b
# a - b
def __sub__(a, b):
try:
return rat(a.__num * b.__den - b.__num * a.__den,
a.__den * b.__den)
except OverflowError:
return rat(long(a.__num) * long(b.__den) -
long(b.__num) * long(a.__den),
long(a.__den) * long(b.__den))
def __rsub__(b, a):
return Rat(a) - b
# a * b
def __mul__(a, b):
try:
return rat(a.__num * b.__num, a.__den * b.__den)
except OverflowError:
return rat(long(a.__num) * long(b.__num),
long(a.__den) * long(b.__den))
def __rmul__(b, a):
return Rat(a) * b
# a / b
def __div__(a, b):
try:
return rat(a.__num * b.__den, a.__den * b.__num)
except OverflowError:
return rat(long(a.__num) * long(b.__den),
long(a.__den) * long(b.__num))
def __rdiv__(b, a):
return Rat(a) / b
# a % b
def __mod__(a, b):
div = a / b
try:
div = int(div)
except OverflowError:
div = long(div)
return a - b * div
def __rmod__(b, a):
return Rat(a) % b
# a ** b
def __pow__(a, b):
if b.__den != 1:
if isinstance(a.__num, complex):
a = complex(a)
else:
a = float(a)
if isinstance(b.__num, complex):
b = complex(b)
else:
b = float(b)
return a ** b
try:
return rat(a.__num ** b.__num, a.__den ** b.__num)
except OverflowError:
return rat(long(a.__num) ** b.__num,
long(a.__den) ** b.__num)
def __rpow__(b, a):
return Rat(a) ** b
# -a
def __neg__(a):
try:
return rat(-a.__num, a.__den)
except OverflowError:
# a.__num == sys.maxint
return rat(-long(a.__num), a.__den)
# abs(a)
def __abs__(a):
return rat(abs(a.__num), a.__den)
# int(a)
def __int__(a):
return int(a.__num / a.__den)
# long(a)
def __long__(a):
return long(a.__num) / long(a.__den)
# float(a)
def __float__(a):
return float(a.__num) / float(a.__den)
# complex(a)
def __complex__(a):
return complex(a.__num) / complex(a.__den)
# cmp(a,b)
def __cmp__(a, b):
diff = Rat(a - b)
if diff.__num < 0:
return -1
elif diff.__num > 0:
return 1
else:
return 0
def __rcmp__(b, a):
return cmp(Rat(a), b)
# a != 0
def __nonzero__(a):
return a.__num != 0
# coercion
def __coerce__(a, b):
return a, Rat(b)
def test():
'''\
Test function for rat module.
The expected output is (module some differences in floating
precission):
-1
-1
0 0L 0.1 (0.1+0j)
[Rat(1,2), Rat(-3,10), Rat(1,25), Rat(1,4)]
[Rat(-3,10), Rat(1,25), Rat(1,4), Rat(1,2)]
0
(11/10)
(11/10)
1.1
OK
2 1.5 (3/2) (1.5+1.5j) (15707963/5000000)
2 2 2.0 (2+0j)
4 0 4 1 4 0
3.5 0.5 3.0 1.33333333333 2.82842712475 1
(7/2) (1/2) 3 (4/3) 2.82842712475 1
(3.5+1.5j) (0.5-1.5j) (3+3j) (0.666666666667-0.666666666667j) (1.43248815986+2.43884761145j) 1
1.5 1 1.5 (1.5+0j)
3.5 -0.5 3.0 0.75 2.25 -1
3.0 0.0 2.25 1.0 1.83711730709 0
3.0 0.0 2.25 1.0 1.83711730709 1
(3+1.5j) -1.5j (2.25+2.25j) (0.5-0.5j) (1.50768393746+1.04970907623j) -1
(3/2) 1 1.5 (1.5+0j)
(7/2) (-1/2) 3 (3/4) (9/4) -1
3.0 0.0 2.25 1.0 1.83711730709 -1
3 0 (9/4) 1 1.83711730709 0
(3+1.5j) -1.5j (2.25+2.25j) (0.5-0.5j) (1.50768393746+1.04970907623j) -1
(1.5+1.5j) (1.5+1.5j)
(3.5+1.5j) (-0.5+1.5j) (3+3j) (0.75+0.75j) 4.5j -1
(3+1.5j) 1.5j (2.25+2.25j) (1+1j) (1.18235814075+2.85446505899j) 1
(3+1.5j) 1.5j (2.25+2.25j) (1+1j) (1.18235814075+2.85446505899j) 1
(3+3j) 0j 4.5j (1+0j) (-0.638110484918+0.705394566962j) 0
'''
print rat(-1L, 1)
print rat(1, -1)
a = rat(1, 10)
print int(a), long(a), float(a), complex(a)
b = rat(2, 5)
l = [a+b, a-b, a*b, a/b]
print l
l.sort()
print l
print rat(0, 1)
print a+1
print a+1L
print a+1.0
try:
print rat(1, 0)
raise SystemError, 'should have been ZeroDivisionError'
except ZeroDivisionError:
print 'OK'
print rat(2), rat(1.5), rat(3, 2), rat(1.5+1.5j), rat(31415926,10000000)
list = [2, 1.5, rat(3,2), 1.5+1.5j]
for i in list:
print i,
if not isinstance(i, complex):
print int(i), float(i),
print complex(i)
print
for j in list:
print i + j, i - j, i * j, i / j, i ** j,
if not (isinstance(i, complex) or
isinstance(j, complex)):
print cmp(i, j)
print
if __name__ == '__main__':
test()
......@@ -21,6 +21,7 @@ The following modules are documented in this chapter:
math.rst
cmath.rst
decimal.rst
rational.rst
random.rst
itertools.rst
functools.rst
......
:mod:`rational` --- Rational numbers
====================================
.. module:: rational
:synopsis: Rational numbers.
.. moduleauthor:: Jeffrey Yasskin <jyasskin at gmail.com>
.. sectionauthor:: Jeffrey Yasskin <jyasskin at gmail.com>
.. versionadded:: 2.6
The :mod:`rational` module defines an immutable, infinite-precision
Rational number class.
.. class:: Rational(numerator=0, denominator=1)
Rational(other_rational)
The first version requires that *numerator* and *denominator* are
instances of :class:`numbers.Integral` and returns a new
``Rational`` representing ``numerator/denominator``. If
*denominator* is :const:`0`, raises a :exc:`ZeroDivisionError`. The
second version requires that *other_rational* is an instance of
:class:`numbers.Rational` and returns an instance of
:class:`Rational` with the same value.
Implements all of the methods and operations from
:class:`numbers.Rational` and is hashable.
.. method:: Rational.from_float(flt)
This classmethod constructs a :class:`Rational` representing the
exact value of *flt*, which must be a :class:`float`. Beware that
``Rational.from_float(0.3)`` is not the same value as ``Rational(3,
10)``
.. method:: Rational.__floor__()
Returns the greatest :class:`int` ``<= self``. Will be accessible
through :func:`math.floor` in Py3k.
.. method:: Rational.__ceil__()
Returns the least :class:`int` ``>= self``. Will be accessible
through :func:`math.ceil` in Py3k.
.. method:: Rational.__round__()
Rational.__round__(ndigits)
The first version returns the nearest :class:`int` to ``self``,
rounding half to even. The second version rounds ``self`` to the
nearest multiple of ``Rational(1, 10**ndigits)`` (logically, if
``ndigits`` is negative), again rounding half toward even. Will be
accessible through :func:`round` in Py3k.
.. seealso::
Module :mod:`numbers`
The abstract base classes making up the numeric tower.
......@@ -5,6 +5,7 @@
TODO: Fill out more detailed documentation on the operators."""
from __future__ import division
from abc import ABCMeta, abstractmethod, abstractproperty
__all__ = ["Number", "Exact", "Inexact",
......@@ -63,7 +64,8 @@ class Complex(Number):
def __complex__(self):
"""Return a builtin complex instance. Called for complex(self)."""
def __bool__(self):
# Will be __bool__ in 3.0.
def __nonzero__(self):
"""True if self != 0. Called for bool(self)."""
return self != 0
......@@ -98,6 +100,7 @@ class Complex(Number):
"""-self"""
raise NotImplementedError
@abstractmethod
def __pos__(self):
"""+self"""
raise NotImplementedError
......@@ -122,12 +125,28 @@ class Complex(Number):
@abstractmethod
def __div__(self, other):
"""self / other; should promote to float or complex when necessary."""
"""self / other without __future__ division
May promote to float.
"""
raise NotImplementedError
@abstractmethod
def __rdiv__(self, other):
"""other / self"""
"""other / self without __future__ division"""
raise NotImplementedError
@abstractmethod
def __truediv__(self, other):
"""self / other with __future__ division.
Should promote to float when necessary.
"""
raise NotImplementedError
@abstractmethod
def __rtruediv__(self, other):
"""other / self with __future__ division"""
raise NotImplementedError
@abstractmethod
......
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment