#!/usr/bin/env python"
"""`rational` an Implementation of Rational Numbers
The module provides rational arithmetic.
Additionally the module servers as example for the generic function package.
Usually you only need its :class:`Rational` class:
>>> from rational import Rational as R
Rational numbers can be constructed from integers:
>>> r2 = R(1, 2)
>>> r1 = R(1)
>>> r0 = R()
Construction from arbitrary objects is not possible:
>>> R("Urmel") # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
NotImplementedError: Generic 'gf.go.__init__' has no implementation for type(s): rational.Rational, __builtin__.str
Rationals also have a decent string representation:
>>> r0
Rational()
>>> print(r0)
0
>>> r1
Rational(1)
>>> print(r1)
1
>>> r2
Rational(1, 2)
>>> print(r2)
1 / 2
Ordinary arithmetic works as expected:
>>> print(R(1, 2) + R(1, 4))
3 / 4
>>> 1 + R(1, 2)
Rational(3, 2)
>>> print(R(2) / 1000)
1 / 500
>>> print(R(-5,-10))
1 / 2
>>> print(R(5, -10))
-1 / 2
>>> print(-R(5, -10))
1 / 2
Comparison also works as expected:
>>> R(1, 2) == R(2, 4)
True
>>> R(4, 2) == 2
True
>>> 1 == R(1, 2)
False
>>> 3 == R(10, 5)
False
>>> R(1, 2) < R(3, 4)
True
>>> R(1, 2) < 1
True
>>> R(1, 2) < 1
True
>>> R(1, 2) > R(1, 4)
True
>>> 1 > R(1, 2)
True
>>> 2 > R(10, 7)
True
>>> R(10, 2) >= R(5)
True
>>> R() != R(1)
True
>>> R() != 0
False
>>> 1 != R(1)
False
The :mod:`decimal` module is supported as well:
>>> from decimal import Decimal as D
>>> R(D("0.375"))
Rational(3, 8)
>>> R(1, 2) + D("1.5")
Rational(2)
Even very long decimals do work:
>>> R(D("7.9864829273648218372937") * 4)
Rational(79864829273648218372937, 2500000000000000000000)
Comparisons with :class:`decimal.Decimal` instances are also supported:
>>> D("1.2") == R(24, 20)
True
>>> D("1.2") >= R(23, 20)
True
>>> R(23, 20) <= D("1.2")
True
Rationals can also converted to floats:
>>> float(R(1, 4))
0.25
"""
from decimal import Decimal
# Make `gf` importable from the examples directory
_python_path_tweaked = False
while 1:
try:
from gf import (method, Object,
__init__, __float__,
__add__, __sub__, __mul__, __truediv__, __neg__,
__eq__, __ne__, __lt__, __gt__, __le__, __ge__,
__out__, __spy__, Writer)
except ImportError:
if _python_path_tweaked:
raise
else:
import sys, os
sys.path.insert(0,
os.path.abspath(
os.path.normpath(
os.path.join(
os.path.dirname(__file__), os.pardir))))
_python_path_tweaked = True
del sys, os
else:
break
[docs]def gcd(a, b):
""":func:`gcd` computes GCD of to numbers."""
while b != 0:
a, b = b, a % b
return a
[docs]class Rational(Object):
""":class:`Rational` is our rational numbers class."""
def f(a: int): pass
@method()
def __init__(rational: Rational, numerator: int, denominator: int, cancel: bool):
"""Initialize the object with `numerator` and `denominator`.
:param rational: The rational number to be initialized.
:param numerator: The numerator.
:param denominator: The denominator.
:param cancel: A flag indicating, that `numerator`and `denominator`
should be canceled.
"""
if denominator == 0:
raise ZeroDivisionError("Denominator can not be zero")
if cancel:
g = gcd( numerator, denominator )
n = numerator // g
d = denominator // g
else:
n = numerator
d = denominator
rational.numerator = n
rational.denominator = d
CANCEL_EAGERLY = True
@method()
def __init__(rational: Rational, numerator: int, denominator: int):
"""Initialize the object with `numerator` and `denominator`.
:param rational: The rational number to be initialized.
:param numerator: The numerator.
:param denominator: The denominator.
Call :func:`__init__` with all passed arguments and
with the value of `CANCEL_EAGERLY` for the `cancel`-flag.
"""
__init__(rational, numerator, denominator, CANCEL_EAGERLY)
@method()
def __init__(rational: Rational, numerator: int):
"""Initialize the object with `numerator`.
:param rational: The rational number to be initialized.
:param numerator: The numerator.
Call :func:`__init__` with the `denominator` set to 1."""
__init__(rational, numerator, 1)
@method()
def __init__(rational: Rational):
"""Initialize the object to be 0.
:param rational: The rational number to be initialized.
Call :func:`__init__` with the `numerator` set to 0."""
__init__(rational, 0)
@method()
def __init__(rational0: Rational, rational1: Rational):
"""Initialize the object from another rational.
:param rational0: The rational number to be initialized.
:param rational1: The rational number the attributes are copied from.
"""
rational0.numerator = rational1.numerator
rational0.denominator = rational1.denominator
@method()
def __init__(rational0: Rational, rational1: Rational, rational2: Rational):
"""Initialize the object from another rational.
:param rational0: The rational number to be initialized.
:param rational1: The rational acting as `numerator.`
:param rational2: The rational acting as `denominator.`
Call :func:`__init__` with `rational0` as `numerator`
and `rational1` / `rational2` as `denominator`."""
__init__(rational0, rational1 // rational2)
@method()
def __init__(rational: Rational, decimal: Decimal):
"""Initialize the object from a :class:`decimal.Decimal`.
:param rational: The rational number to be initialized.
:param decimal: The decimal number the rational is initialized from.
If the `decimal`'s exponent is negative compute a scaling
denominator 10 ** -exponent and initialise `rational`
with the decimal scaled by the denominator and the denominator.
In the other case the `decimal` is simply converted to an
`int` and used as numerator."""
exponent = decimal.as_tuple().exponent
if exponent < 0:
denominator = Decimal((0, (1,), -exponent))
__init__(rational,
int(decimal * denominator),
int(denominator))
else:
__init__(rational, int(decimal))
@method()
def __float__(rational: Rational):
"""Convert a rational to a float."""
return float(rational.numerator) / float(rational.denominator)
@method()
def __out__(rational: Rational, writer: Writer):
"""Write a nice representation of the rational.
Denominators that equal 1 are not printed."""
writer("%d", rational.numerator)
if rational.denominator != 1:
writer(" / %d", rational.denominator)
@method()
def __spy__(rational: Rational, writer: Writer):
"""Write a debug representation of the rational."""
writer("%s(", rational.__class__.__name__)
if rational.numerator != 0:
writer("%r", rational.numerator)
if rational.denominator != 1:
writer(", %r", rational.denominator)
writer(")")
@method()
def __add__(a: Rational, b: Rational):
"""Add two rational numbers."""
return Rational(a.numerator * b.denominator + b.numerator * a.denominator,
a.denominator * b.denominator)
@method()
def __add__(a, b: Rational):
"""Add an object and a rational number.
`a` is converted to a :class:`Rational` and then both are added."""
return Rational(a) + b
@method()
def __add__(a: Rational, b):
"""Add a rational number and an object.
`b` is converted to a :class:`Rational` and then both are added."""
return a + Rational(b)
@method()
def __sub__(a: Rational, b: Rational):
"""Subtract two rational numbers."""
return Rational(a.numerator * b.denominator - b.numerator * a.denominator,
a.denominator * b.denominator)
@method()
def __sub__(a, b: Rational):
"""Subtract an object and a rational number.
`a` is converted to a :class:`Rational` and then both are subtracted."""
return Rational(a) - b
@method()
def __sub__(a: Rational, b):
"""Subtract a rational number and an object.
`b` is converted to a :class:`Rational` and then both are subtracted."""
return a - Rational(b)
@method()
def __mul__(a: Rational, b: Rational):
"""Multiply two rational numbers."""
return Rational(a.numerator * b.numerator, a.denominator * b.denominator)
@method()
def __mul__(a, b: Rational):
"""Multiply an object and a rational number.
`a` is converted to a :class:`Rational` and then both are multiplied."""
return Rational(a) * b
@method()
def __mul__(a, b: Rational):
"""Multiply a rational and an object.
`b` is converted to a :class:`Rational` and then both are multiplied."""
return a * Rational(b)
@method()
def __truediv__(a: Rational, b: Rational):
"""Divide two rational numbers."""
return Rational(a.numerator * b.denominator, a.denominator * b.numerator)
@method()
def __truediv__(a, b: Rational):
"""Divide an object and a rational number.
`a` is converted to a :class:`Rational` and then both are divided."""
return Rational(a) / b
@method()
def __truediv__(a: Rational, b):
"""Divide a rational and an object.
`b` is converted to a :class:`Rational` and then both are divided."""
return a / Rational(b)
@method()
def __neg__(rational: Rational):
"""Negate a rational number."""
return Rational(-rational.numerator, rational.denominator)
@method()
def __eq__(a: Rational, b: Rational):
"""Compare to rational numbers for equality."""
return a.numerator == b.numerator and a.denominator == b.denominator
@method()
def __eq__(a: Rational, b):
"""Compare a rational numbers and another object for equality."""
return a == Rational(b)
@method()
def __eq__(a: Rational, b: int):
"""Compare a rational numbers and an integer for equality.
:Note: This is an optimisation for `int`."""
return a.denominator == 1 and a.numerator == b
@method()
def __ne__(a: Rational, b: Rational):
"""Compare to rational numbers for inequality."""
return a.numerator != b.numerator or a.denominator != b.denominator
@method()
def __ne__(a: Rational, b):
"""Compare to rational numbers for inequality."""
return a != Rational(b)
@method()
def __lt__(a: Rational, b: Rational):
"""Answer `True` if `a` is smaller than `b`."""
return (b - a).numerator > 0
@method()
def __lt__(a: Rational, b):
"""Answer `True` if `a` is smaller than `b`."""
return a < Rational(b)
@method()
def __le__(a: Rational, b: Rational):
"""Answer `True` if `a` is smaller than or equal `b`."""
return (b - a).numerator >= 0
@method()
def __le__(a: Rational, b):
"""Answer `True` if `a` is smaller than or equal `b`."""
return a <= Rational(b)
@method()
def __gt__(a: Rational, b: Rational):
"""Answer `True` if `a` is bigger than `b`."""
return (a - b).numerator > 0
@method()
def __gt__(a: Rational, b):
"""Answer `True` if `a` is bigger than `b`."""
return a > Rational(b)
@method()
def __ge__(a: Rational, b: Rational):
"""Answer `True` if `a` is bigger or equal than `b`."""
return (a - b).numerator >= 0
@method()
def __ge__(a: Rational, b):
"""Answer `True` if `a` is bigger or equal than `b`."""
return a >= Rational(b)
if __name__ == "__main__":
import doctest
doctest.testmod()