From d058cd2cc8e2a3f61609b92a8fc821ea8ec524ca Mon Sep 17 00:00:00 2001 From: Mark Dickinson Date: Sun, 10 Feb 2008 21:29:51 +0000 Subject: Rename rational.Rational to fractions.Fraction, to avoid name clash with numbers.Rational. See issue #1682 for related discussion. --- Doc/library/fractions.rst | 75 +++++++ Doc/library/numbers.rst | 10 +- Doc/library/rational.rst | 75 ------- Doc/whatsnew/2.6.rst | 22 +- Lib/fractions.py | 520 +++++++++++++++++++++++++++++++++++++++++++++ Lib/rational.py | 520 --------------------------------------------- Lib/test/test_builtin.py | 4 +- Lib/test/test_fractions.py | 412 +++++++++++++++++++++++++++++++++++ Lib/test/test_rational.py | 412 ----------------------------------- Misc/NEWS | 4 + 10 files changed, 1029 insertions(+), 1025 deletions(-) create mode 100644 Doc/library/fractions.rst delete mode 100644 Doc/library/rational.rst create mode 100755 Lib/fractions.py delete mode 100755 Lib/rational.py create mode 100644 Lib/test/test_fractions.py delete mode 100644 Lib/test/test_rational.py diff --git a/Doc/library/fractions.rst b/Doc/library/fractions.rst new file mode 100644 index 0000000..af6ed76 --- /dev/null +++ b/Doc/library/fractions.rst @@ -0,0 +1,75 @@ + +:mod:`fractions` --- Rational numbers +==================================== + +.. module:: fractions + :synopsis: Rational numbers. +.. moduleauthor:: Jeffrey Yasskin +.. sectionauthor:: Jeffrey Yasskin +.. versionadded:: 2.6 + + +The :mod:`fractions` module defines an immutable, infinite-precision +Fraction number class. + + +.. class:: Fraction(numerator=0, denominator=1) + Fraction(other_fraction) + Fraction(string) + + The first version requires that *numerator* and *denominator* are + instances of :class:`numbers.Integral` and returns a new + ``Fraction`` representing ``numerator/denominator``. If + *denominator* is :const:`0`, raises a :exc:`ZeroDivisionError`. The + second version requires that *other_fraction* is an instance of + :class:`numbers.Rational` and returns an instance of + :class:`Fraction` with the same value. The third version expects a + string of the form ``[-+]?[0-9]+(/[0-9]+)?``, optionally surrounded + by spaces. + + Implements all of the methods and operations from + :class:`numbers.Rational` and is immutable and hashable. + + +.. method:: Fraction.from_float(flt) + + This classmethod constructs a :class:`Fraction` representing the + exact value of *flt*, which must be a :class:`float`. Beware that + ``Fraction.from_float(0.3)`` is not the same value as ``Fraction(3, + 10)`` + + +.. method:: Fraction.from_decimal(dec) + + This classmethod constructs a :class:`Fraction` representing the + exact value of *dec*, which must be a + :class:`decimal.Decimal`. + + +.. method:: Fraction.__floor__() + + Returns the greatest :class:`int` ``<= self``. Will be accessible + through :func:`math.floor` in Py3k. + + +.. method:: Fraction.__ceil__() + + Returns the least :class:`int` ``>= self``. Will be accessible + through :func:`math.ceil` in Py3k. + + +.. method:: Fraction.__round__() + Fraction.__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 ``Fraction(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. + diff --git a/Doc/library/numbers.rst b/Doc/library/numbers.rst index 6ee8f27..7a5f105 100644 --- a/Doc/library/numbers.rst +++ b/Doc/library/numbers.rst @@ -106,7 +106,7 @@ Notes for type implementors Implementors should be careful to make equal numbers equal and hash them to the same values. This may be subtle if there are two different -extensions of the real numbers. For example, :class:`rational.Rational` +extensions of the real numbers. For example, :class:`fractions.Fraction` implements :func:`hash` as follows:: def __hash__(self): @@ -201,11 +201,11 @@ in :class:`complex`, and both :meth:`__radd__` s land there, so ``a+b Because most of the operations on any given type will be very similar, it can be useful to define a helper function which generates the forward and reverse instances of any given operator. For example, -:class:`rational.Rational` uses:: +:class:`fractions.Fraction` uses:: def _operator_fallbacks(monomorphic_operator, fallback_operator): def forward(a, b): - if isinstance(b, (int, long, Rational)): + if isinstance(b, (int, long, Fraction)): return monomorphic_operator(a, b) elif isinstance(b, float): return fallback_operator(float(a), b) @@ -217,7 +217,7 @@ forward and reverse instances of any given operator. For example, forward.__doc__ = monomorphic_operator.__doc__ def reverse(b, a): - if isinstance(a, RationalAbc): + if isinstance(a, Rational): # Includes ints. return monomorphic_operator(a, b) elif isinstance(a, numbers.Real): @@ -233,7 +233,7 @@ forward and reverse instances of any given operator. For example, def _add(a, b): """a + b""" - return Rational(a.numerator * b.denominator + + return Fraction(a.numerator * b.denominator + b.numerator * a.denominator, a.denominator * b.denominator) diff --git a/Doc/library/rational.rst b/Doc/library/rational.rst deleted file mode 100644 index 8ed702f..0000000 --- a/Doc/library/rational.rst +++ /dev/null @@ -1,75 +0,0 @@ - -:mod:`rational` --- Rational numbers -==================================== - -.. module:: rational - :synopsis: Rational numbers. -.. moduleauthor:: Jeffrey Yasskin -.. sectionauthor:: Jeffrey Yasskin -.. versionadded:: 2.6 - - -The :mod:`rational` module defines an immutable, infinite-precision -Rational number class. - - -.. class:: Rational(numerator=0, denominator=1) - Rational(other_rational) - Rational(string) - - 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. The third version expects a - string of the form ``[-+]?[0-9]+(/[0-9]+)?``, optionally surrounded - by spaces. - - Implements all of the methods and operations from - :class:`numbers.Rational` and is immutable and 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.from_decimal(dec) - - This classmethod constructs a :class:`Rational` representing the - exact value of *dec*, which must be a - :class:`decimal.Decimal`. - - -.. 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. - diff --git a/Doc/whatsnew/2.6.rst b/Doc/whatsnew/2.6.rst index cbc8b8f..83cca99 100644 --- a/Doc/whatsnew/2.6.rst +++ b/Doc/whatsnew/2.6.rst @@ -578,8 +578,8 @@ and comparisons. :class:`Rational` numbers derive from :class:`Real`, have :attr:`numerator` and :attr:`denominator` properties, and can be -converted to floats. Python 2.6 adds a simple rational-number class -in the :mod:`rational` module. +converted to floats. Python 2.6 adds a simple rational-number class, +:class:`Fraction`, in the :mod:`fractions` module. :class:`Integral` numbers derive from :class:`Rational`, and can be shifted left and right with ``<<`` and ``>>``, @@ -598,29 +598,29 @@ one, :func:`trunc`, that's been backported to Python 2.6. -The Rational Module +The Fraction Module -------------------------------------------------- To fill out the hierarchy of numeric types, a rational-number class -has been added as the :mod:`rational` module. Rational numbers are +has been added as the :mod:`fractions` module. Rational numbers are represented as a fraction; rational numbers can exactly represent numbers such as two-thirds that floating-point numbers can only approximate. -The :class:`Rational` constructor takes two :class:`Integral` values +The :class:`Fraction` constructor takes two :class:`Integral` values that will be the numerator and denominator of the resulting fraction. :: - >>> from rational import Rational - >>> a = Rational(2, 3) - >>> b = Rational(2, 5) + >>> from fractions import Fraction + >>> a = Fraction(2, 3) + >>> b = Fraction(2, 5) >>> float(a), float(b) (0.66666666666666663, 0.40000000000000002) >>> a+b - rational.Rational(16,15) + Fraction(16,15) >>> a/b - rational.Rational(5,3) + Fraction(5,3) -The :mod:`rational` module is based upon an implementation by Sjoerd +The :mod:`fractions` module is based upon an implementation by Sjoerd Mullender that was in Python's :file:`Demo/classes/` directory for a long time. This implementation was significantly updated by Jeffrey Yaskin. diff --git a/Lib/fractions.py b/Lib/fractions.py new file mode 100755 index 0000000..3f070de --- /dev/null +++ b/Lib/fractions.py @@ -0,0 +1,520 @@ +# Originally contributed by Sjoerd Mullender. +# Significantly modified by Jeffrey Yasskin . + +"""Rational, infinite-precision, real numbers.""" + +from __future__ import division +import math +import numbers +import operator +import re + +__all__ = ["Fraction"] + +Rational = numbers.Rational + + +def gcd(a, b): + """Calculate the Greatest Common Divisor of a and b. + + Unless b==0, the result will have the same sign as b (so that when + b is divided by it, the result comes out positive). + """ + while b: + a, b = b, a%b + return a + + +_RATIONAL_FORMAT = re.compile(r""" + \A\s* # optional whitespace at the start, then + (?P[-+]?) # an optional sign, then + (?=\d|\.\d) # lookahead for digit or .digit + (?P\d*) # numerator (possibly empty) + (?: # followed by an optional + /(?P\d+) # / and denominator + | # or + \.(?P\d*) # decimal point and fractional part + )? + \s*\Z # and optional whitespace to finish +""", re.VERBOSE) + + +class Fraction(Rational): + """This class implements rational numbers. + + Fraction(8, 6) will produce a rational number equivalent to + 4/3. Both arguments must be Integral. The numerator defaults to 0 + and the denominator defaults to 1 so that Fraction(3) == 3 and + Fraction() == 0. + + Fractions can also be constructed from strings of the form + '[-+]?[0-9]+((/|.)[0-9]+)?', optionally surrounded by spaces. + + """ + + __slots__ = ('_numerator', '_denominator') + + # We're immutable, so use __new__ not __init__ + def __new__(cls, numerator=0, denominator=1): + """Constructs a Fraction. + + Takes a string like '3/2' or '1.5', another Fraction, or a + numerator/denominator pair. + + """ + self = super(Fraction, cls).__new__(cls) + + if denominator == 1: + if isinstance(numerator, basestring): + # Handle construction from strings. + input = numerator + m = _RATIONAL_FORMAT.match(input) + if m is None: + raise ValueError('Invalid literal for Fraction: ' + input) + numerator = m.group('num') + decimal = m.group('decimal') + if decimal: + # The literal is a decimal number. + numerator = int(numerator + decimal) + denominator = 10**len(decimal) + else: + # The literal is an integer or fraction. + numerator = int(numerator) + # Default denominator to 1. + denominator = int(m.group('denom') or 1) + + if m.group('sign') == '-': + numerator = -numerator + + elif (not isinstance(numerator, numbers.Integral) and + isinstance(numerator, Rational)): + # Handle copies from other rationals. + other_rational = numerator + numerator = other_rational.numerator + denominator = other_rational.denominator + + if (not isinstance(numerator, numbers.Integral) or + not isinstance(denominator, numbers.Integral)): + raise TypeError("Fraction(%(numerator)s, %(denominator)s):" + " Both arguments must be integral." % locals()) + + if denominator == 0: + raise ZeroDivisionError('Fraction(%s, 0)' % numerator) + + g = gcd(numerator, denominator) + self._numerator = int(numerator // g) + self._denominator = int(denominator // g) + return self + + @staticmethod + def from_float(f): + """Converts a finite float to a rational number, exactly. + + Beware that Fraction.from_float(0.3) != Fraction(3, 10). + + """ + if not isinstance(f, float): + raise TypeError("Fraction.from_float() only takes floats, " + "not %r (%s)" % (f, type(f).__name__)) + if math.isnan(f) or math.isinf(f): + raise TypeError("Cannot convert %r to Fraction." % f) + return Fraction(*f.as_integer_ratio()) + + @staticmethod + def from_decimal(dec): + """Converts a finite Decimal instance to a rational number, exactly.""" + from decimal import Decimal + if not isinstance(dec, Decimal): + raise TypeError( + "Fraction.from_decimal() only takes Decimals, not %r (%s)" % + (dec, type(dec).__name__)) + if not dec.is_finite(): + # Catches infinities and nans. + raise TypeError("Cannot convert %s to Fraction." % dec) + sign, digits, exp = dec.as_tuple() + digits = int(''.join(map(str, digits))) + if sign: + digits = -digits + if exp >= 0: + return Fraction(digits * 10 ** exp) + else: + return Fraction(digits, 10 ** -exp) + + @staticmethod + def from_continued_fraction(seq): + 'Build a Fraction from a continued fraction expessed as a sequence' + n, d = 1, 0 + for e in reversed(seq): + n, d = d, n + n += e * d + return Fraction(n, d) if seq else Fraction(0) + + def as_continued_fraction(self): + 'Return continued fraction expressed as a list' + n = self.numerator + d = self.denominator + cf = [] + while d: + e = int(n // d) + cf.append(e) + n -= e * d + n, d = d, n + return cf + + def approximate(self, max_denominator): + 'Best rational approximation with a denominator <= max_denominator' + # XXX First cut at algorithm + # Still needs rounding rules as specified at + # http://en.wikipedia.org/wiki/Continued_fraction + if self.denominator <= max_denominator: + return self + cf = self.as_continued_fraction() + result = Fraction(0) + for i in range(1, len(cf)): + new = self.from_continued_fraction(cf[:i]) + if new.denominator > max_denominator: + break + result = new + return result + + @property + def numerator(a): + return a._numerator + + @property + def denominator(a): + return a._denominator + + def __repr__(self): + """repr(self)""" + return ('Fraction(%r,%r)' % (self.numerator, self.denominator)) + + def __str__(self): + """str(self)""" + if self.denominator == 1: + return str(self.numerator) + else: + return '%s/%s' % (self.numerator, self.denominator) + + def _operator_fallbacks(monomorphic_operator, fallback_operator): + """Generates forward and reverse operators given a purely-rational + operator and a function from the operator module. + + Use this like: + __op__, __rop__ = _operator_fallbacks(just_rational_op, operator.op) + + In general, we want to implement the arithmetic operations so + that mixed-mode operations either call an implementation whose + author knew about the types of both arguments, or convert both + to the nearest built in type and do the operation there. In + Fraction, that means that we define __add__ and __radd__ as: + + def __add__(self, other): + # Both types have numerators/denominator attributes, + # so do the operation directly + if isinstance(other, (int, long, Fraction)): + return Fraction(self.numerator * other.denominator + + other.numerator * self.denominator, + self.denominator * other.denominator) + # float and complex don't have those operations, but we + # know about those types, so special case them. + elif isinstance(other, float): + return float(self) + other + elif isinstance(other, complex): + return complex(self) + other + # Let the other type take over. + return NotImplemented + + def __radd__(self, other): + # radd handles more types than add because there's + # nothing left to fall back to. + if isinstance(other, Rational): + return Fraction(self.numerator * other.denominator + + other.numerator * self.denominator, + self.denominator * other.denominator) + elif isinstance(other, Real): + return float(other) + float(self) + elif isinstance(other, Complex): + return complex(other) + complex(self) + return NotImplemented + + + There are 5 different cases for a mixed-type addition on + Fraction. I'll refer to all of the above code that doesn't + refer to Fraction, float, or complex as "boilerplate". 'r' + will be an instance of Fraction, which is a subtype of + Rational (r : Fraction <: Rational), and b : B <: + Complex. The first three involve 'r + b': + + 1. If B <: Fraction, int, float, or complex, we handle + that specially, and all is well. + 2. If Fraction falls back to the boilerplate code, and it + were to return a value from __add__, we'd miss the + possibility that B defines a more intelligent __radd__, + so the boilerplate should return NotImplemented from + __add__. In particular, we don't handle Rational + here, even though we could get an exact answer, in case + the other type wants to do something special. + 3. If B <: Fraction, Python tries B.__radd__ before + Fraction.__add__. This is ok, because it was + implemented with knowledge of Fraction, so it can + handle those instances before delegating to Real or + Complex. + + The next two situations describe 'b + r'. We assume that b + didn't know about Fraction in its implementation, and that it + uses similar boilerplate code: + + 4. If B <: Rational, then __radd_ converts both to the + builtin rational type (hey look, that's us) and + proceeds. + 5. Otherwise, __radd__ tries to find the nearest common + base ABC, and fall back to its builtin type. Since this + class doesn't subclass a concrete type, there's no + implementation to fall back to, so we need to try as + hard as possible to return an actual value, or the user + will get a TypeError. + + """ + def forward(a, b): + if isinstance(b, (int, long, Fraction)): + return monomorphic_operator(a, b) + elif isinstance(b, float): + return fallback_operator(float(a), b) + elif isinstance(b, complex): + return fallback_operator(complex(a), b) + else: + return NotImplemented + forward.__name__ = '__' + fallback_operator.__name__ + '__' + forward.__doc__ = monomorphic_operator.__doc__ + + def reverse(b, a): + if isinstance(a, Rational): + # Includes ints. + return monomorphic_operator(a, b) + elif isinstance(a, numbers.Real): + return fallback_operator(float(a), float(b)) + elif isinstance(a, numbers.Complex): + return fallback_operator(complex(a), complex(b)) + else: + return NotImplemented + reverse.__name__ = '__r' + fallback_operator.__name__ + '__' + reverse.__doc__ = monomorphic_operator.__doc__ + + return forward, reverse + + def _add(a, b): + """a + b""" + return Fraction(a.numerator * b.denominator + + b.numerator * a.denominator, + a.denominator * b.denominator) + + __add__, __radd__ = _operator_fallbacks(_add, operator.add) + + def _sub(a, b): + """a - b""" + return Fraction(a.numerator * b.denominator - + b.numerator * a.denominator, + a.denominator * b.denominator) + + __sub__, __rsub__ = _operator_fallbacks(_sub, operator.sub) + + def _mul(a, b): + """a * b""" + return Fraction(a.numerator * b.numerator, a.denominator * b.denominator) + + __mul__, __rmul__ = _operator_fallbacks(_mul, operator.mul) + + def _div(a, b): + """a / b""" + return Fraction(a.numerator * b.denominator, + a.denominator * b.numerator) + + __truediv__, __rtruediv__ = _operator_fallbacks(_div, operator.truediv) + __div__, __rdiv__ = _operator_fallbacks(_div, operator.div) + + def __floordiv__(a, b): + """a // b""" + # Will be math.floor(a / b) in 3.0. + div = a / b + if isinstance(div, Rational): + # trunc(math.floor(div)) doesn't work if the rational is + # more precise than a float because the intermediate + # rounding may cross an integer boundary. + return div.numerator // div.denominator + else: + return math.floor(div) + + def __rfloordiv__(b, a): + """a // b""" + # Will be math.floor(a / b) in 3.0. + div = a / b + if isinstance(div, Rational): + # trunc(math.floor(div)) doesn't work if the rational is + # more precise than a float because the intermediate + # rounding may cross an integer boundary. + return div.numerator // div.denominator + else: + return math.floor(div) + + def __mod__(a, b): + """a % b""" + div = a // b + return a - b * div + + def __rmod__(b, a): + """a % b""" + div = a // b + return a - b * div + + def __pow__(a, b): + """a ** b + + If b is not an integer, the result will be a float or complex + since roots are generally irrational. If b is an integer, the + result will be rational. + + """ + if isinstance(b, Rational): + if b.denominator == 1: + power = b.numerator + if power >= 0: + return Fraction(a.numerator ** power, + a.denominator ** power) + else: + return Fraction(a.denominator ** -power, + a.numerator ** -power) + else: + # A fractional power will generally produce an + # irrational number. + return float(a) ** float(b) + else: + return float(a) ** b + + def __rpow__(b, a): + """a ** b""" + if b.denominator == 1 and b.numerator >= 0: + # If a is an int, keep it that way if possible. + return a ** b.numerator + + if isinstance(a, Rational): + return Fraction(a.numerator, a.denominator) ** b + + if b.denominator == 1: + return a ** b.numerator + + return a ** float(b) + + def __pos__(a): + """+a: Coerces a subclass instance to Fraction""" + return Fraction(a.numerator, a.denominator) + + def __neg__(a): + """-a""" + return Fraction(-a.numerator, a.denominator) + + def __abs__(a): + """abs(a)""" + return Fraction(abs(a.numerator), a.denominator) + + def __trunc__(a): + """trunc(a)""" + if a.numerator < 0: + return -(-a.numerator // a.denominator) + else: + return a.numerator // a.denominator + + def __hash__(self): + """hash(self) + + Tricky because values that are exactly representable as a + float must have the same hash as that float. + + """ + # XXX since this method is expensive, consider caching the result + if self.denominator == 1: + # Get integers right. + return hash(self.numerator) + # Expensive check, but definitely correct. + if self == float(self): + return hash(float(self)) + else: + # Use tuple's hash to avoid a high collision rate on + # simple fractions. + return hash((self.numerator, self.denominator)) + + def __eq__(a, b): + """a == b""" + if isinstance(b, Rational): + return (a.numerator == b.numerator and + a.denominator == b.denominator) + if isinstance(b, numbers.Complex) and b.imag == 0: + b = b.real + if isinstance(b, float): + return a == a.from_float(b) + else: + # XXX: If b.__eq__ is implemented like this method, it may + # give the wrong answer after float(a) changes a's + # value. Better ways of doing this are welcome. + return float(a) == b + + def _subtractAndCompareToZero(a, b, op): + """Helper function for comparison operators. + + Subtracts b from a, exactly if possible, and compares the + result with 0 using op, in such a way that the comparison + won't recurse. If the difference raises a TypeError, returns + NotImplemented instead. + + """ + if isinstance(b, numbers.Complex) and b.imag == 0: + b = b.real + if isinstance(b, float): + b = a.from_float(b) + try: + # XXX: If b <: Real but not <: Rational, this is likely + # to fall back to a float. If the actual values differ by + # less than MIN_FLOAT, this could falsely call them equal, + # which would make <= inconsistent with ==. Better ways of + # doing this are welcome. + diff = a - b + except TypeError: + return NotImplemented + if isinstance(diff, Rational): + return op(diff.numerator, 0) + return op(diff, 0) + + def __lt__(a, b): + """a < b""" + return a._subtractAndCompareToZero(b, operator.lt) + + def __gt__(a, b): + """a > b""" + return a._subtractAndCompareToZero(b, operator.gt) + + def __le__(a, b): + """a <= b""" + return a._subtractAndCompareToZero(b, operator.le) + + def __ge__(a, b): + """a >= b""" + return a._subtractAndCompareToZero(b, operator.ge) + + def __nonzero__(a): + """a != 0""" + return a.numerator != 0 + + # support for pickling, copy, and deepcopy + + def __reduce__(self): + return (self.__class__, (str(self),)) + + def __copy__(self): + if type(self) == Fraction: + return self # I'm immutable; therefore I am my own clone + return self.__class__(self.numerator, self.denominator) + + def __deepcopy__(self, memo): + if type(self) == Fraction: + return self # My components are also immutable + return self.__class__(self.numerator, self.denominator) diff --git a/Lib/rational.py b/Lib/rational.py deleted file mode 100755 index b45da13..0000000 --- a/Lib/rational.py +++ /dev/null @@ -1,520 +0,0 @@ -# Originally contributed by Sjoerd Mullender. -# Significantly modified by Jeffrey Yasskin . - -"""Rational, infinite-precision, real numbers.""" - -from __future__ import division -import math -import numbers -import operator -import re - -__all__ = ["Rational"] - -RationalAbc = numbers.Rational - - -def gcd(a, b): - """Calculate the Greatest Common Divisor of a and b. - - Unless b==0, the result will have the same sign as b (so that when - b is divided by it, the result comes out positive). - """ - while b: - a, b = b, a%b - return a - - -_RATIONAL_FORMAT = re.compile(r""" - \A\s* # optional whitespace at the start, then - (?P[-+]?) # an optional sign, then - (?=\d|\.\d) # lookahead for digit or .digit - (?P\d*) # numerator (possibly empty) - (?: # followed by an optional - /(?P\d+) # / and denominator - | # or - \.(?P\d*) # decimal point and fractional part - )? - \s*\Z # and optional whitespace to finish -""", re.VERBOSE) - - -class Rational(RationalAbc): - """This class implements rational numbers. - - Rational(8, 6) will produce a rational number equivalent to - 4/3. Both arguments must be Integral. The numerator defaults to 0 - and the denominator defaults to 1 so that Rational(3) == 3 and - Rational() == 0. - - Rationals can also be constructed from strings of the form - '[-+]?[0-9]+((/|.)[0-9]+)?', optionally surrounded by spaces. - - """ - - __slots__ = ('_numerator', '_denominator') - - # We're immutable, so use __new__ not __init__ - def __new__(cls, numerator=0, denominator=1): - """Constructs a Rational. - - Takes a string like '3/2' or '1.5', another Rational, or a - numerator/denominator pair. - - """ - self = super(Rational, cls).__new__(cls) - - if denominator == 1: - if isinstance(numerator, basestring): - # Handle construction from strings. - input = numerator - m = _RATIONAL_FORMAT.match(input) - if m is None: - raise ValueError('Invalid literal for Rational: ' + input) - numerator = m.group('num') - decimal = m.group('decimal') - if decimal: - # The literal is a decimal number. - numerator = int(numerator + decimal) - denominator = 10**len(decimal) - else: - # The literal is an integer or fraction. - numerator = int(numerator) - # Default denominator to 1. - denominator = int(m.group('denom') or 1) - - if m.group('sign') == '-': - numerator = -numerator - - elif (not isinstance(numerator, numbers.Integral) and - isinstance(numerator, RationalAbc)): - # Handle copies from other rationals. - other_rational = numerator - numerator = other_rational.numerator - denominator = other_rational.denominator - - if (not isinstance(numerator, numbers.Integral) or - not isinstance(denominator, numbers.Integral)): - raise TypeError("Rational(%(numerator)s, %(denominator)s):" - " Both arguments must be integral." % locals()) - - if denominator == 0: - raise ZeroDivisionError('Rational(%s, 0)' % numerator) - - g = gcd(numerator, denominator) - self._numerator = int(numerator // g) - self._denominator = int(denominator // g) - return self - - @staticmethod - def from_float(f): - """Converts a finite float to a rational number, exactly. - - Beware that Rational.from_float(0.3) != Rational(3, 10). - - """ - if not isinstance(f, float): - raise TypeError("Rational.from_float() only takes floats, " - "not %r (%s)" % (f, type(f).__name__)) - if math.isnan(f) or math.isinf(f): - raise TypeError("Cannot convert %r to Rational." % f) - return Rational(*f.as_integer_ratio()) - - @staticmethod - def from_decimal(dec): - """Converts a finite Decimal instance to a rational number, exactly.""" - from decimal import Decimal - if not isinstance(dec, Decimal): - raise TypeError( - "Rational.from_decimal() only takes Decimals, not %r (%s)" % - (dec, type(dec).__name__)) - if not dec.is_finite(): - # Catches infinities and nans. - raise TypeError("Cannot convert %s to Rational." % dec) - sign, digits, exp = dec.as_tuple() - digits = int(''.join(map(str, digits))) - if sign: - digits = -digits - if exp >= 0: - return Rational(digits * 10 ** exp) - else: - return Rational(digits, 10 ** -exp) - - @staticmethod - def from_continued_fraction(seq): - 'Build a Rational from a continued fraction expessed as a sequence' - n, d = 1, 0 - for e in reversed(seq): - n, d = d, n - n += e * d - return Rational(n, d) if seq else Rational(0) - - def as_continued_fraction(self): - 'Return continued fraction expressed as a list' - n = self.numerator - d = self.denominator - cf = [] - while d: - e = int(n // d) - cf.append(e) - n -= e * d - n, d = d, n - return cf - - def approximate(self, max_denominator): - 'Best rational approximation with a denominator <= max_denominator' - # XXX First cut at algorithm - # Still needs rounding rules as specified at - # http://en.wikipedia.org/wiki/Continued_fraction - if self.denominator <= max_denominator: - return self - cf = self.as_continued_fraction() - result = Rational(0) - for i in range(1, len(cf)): - new = self.from_continued_fraction(cf[:i]) - if new.denominator > max_denominator: - break - result = new - return result - - @property - def numerator(a): - return a._numerator - - @property - def denominator(a): - return a._denominator - - def __repr__(self): - """repr(self)""" - return ('Rational(%r,%r)' % (self.numerator, self.denominator)) - - def __str__(self): - """str(self)""" - if self.denominator == 1: - return str(self.numerator) - else: - return '%s/%s' % (self.numerator, self.denominator) - - def _operator_fallbacks(monomorphic_operator, fallback_operator): - """Generates forward and reverse operators given a purely-rational - operator and a function from the operator module. - - Use this like: - __op__, __rop__ = _operator_fallbacks(just_rational_op, operator.op) - - In general, we want to implement the arithmetic operations so - that mixed-mode operations either call an implementation whose - author knew about the types of both arguments, or convert both - to the nearest built in type and do the operation there. In - Rational, that means that we define __add__ and __radd__ as: - - def __add__(self, other): - # Both types have numerators/denominator attributes, - # so do the operation directly - if isinstance(other, (int, long, Rational)): - return Rational(self.numerator * other.denominator + - other.numerator * self.denominator, - self.denominator * other.denominator) - # float and complex don't have those operations, but we - # know about those types, so special case them. - elif isinstance(other, float): - return float(self) + other - elif isinstance(other, complex): - return complex(self) + other - # Let the other type take over. - return NotImplemented - - def __radd__(self, other): - # radd handles more types than add because there's - # nothing left to fall back to. - if isinstance(other, RationalAbc): - return Rational(self.numerator * other.denominator + - other.numerator * self.denominator, - self.denominator * other.denominator) - elif isinstance(other, Real): - return float(other) + float(self) - elif isinstance(other, Complex): - return complex(other) + complex(self) - return NotImplemented - - - There are 5 different cases for a mixed-type addition on - Rational. I'll refer to all of the above code that doesn't - refer to Rational, float, or complex as "boilerplate". 'r' - will be an instance of Rational, which is a subtype of - RationalAbc (r : Rational <: RationalAbc), and b : B <: - Complex. The first three involve 'r + b': - - 1. If B <: Rational, int, float, or complex, we handle - that specially, and all is well. - 2. If Rational falls back to the boilerplate code, and it - were to return a value from __add__, we'd miss the - possibility that B defines a more intelligent __radd__, - so the boilerplate should return NotImplemented from - __add__. In particular, we don't handle RationalAbc - here, even though we could get an exact answer, in case - the other type wants to do something special. - 3. If B <: Rational, Python tries B.__radd__ before - Rational.__add__. This is ok, because it was - implemented with knowledge of Rational, so it can - handle those instances before delegating to Real or - Complex. - - The next two situations describe 'b + r'. We assume that b - didn't know about Rational in its implementation, and that it - uses similar boilerplate code: - - 4. If B <: RationalAbc, then __radd_ converts both to the - builtin rational type (hey look, that's us) and - proceeds. - 5. Otherwise, __radd__ tries to find the nearest common - base ABC, and fall back to its builtin type. Since this - class doesn't subclass a concrete type, there's no - implementation to fall back to, so we need to try as - hard as possible to return an actual value, or the user - will get a TypeError. - - """ - def forward(a, b): - if isinstance(b, (int, long, Rational)): - return monomorphic_operator(a, b) - elif isinstance(b, float): - return fallback_operator(float(a), b) - elif isinstance(b, complex): - return fallback_operator(complex(a), b) - else: - return NotImplemented - forward.__name__ = '__' + fallback_operator.__name__ + '__' - forward.__doc__ = monomorphic_operator.__doc__ - - def reverse(b, a): - if isinstance(a, RationalAbc): - # Includes ints. - return monomorphic_operator(a, b) - elif isinstance(a, numbers.Real): - return fallback_operator(float(a), float(b)) - elif isinstance(a, numbers.Complex): - return fallback_operator(complex(a), complex(b)) - else: - return NotImplemented - reverse.__name__ = '__r' + fallback_operator.__name__ + '__' - reverse.__doc__ = monomorphic_operator.__doc__ - - return forward, reverse - - def _add(a, b): - """a + b""" - return Rational(a.numerator * b.denominator + - b.numerator * a.denominator, - a.denominator * b.denominator) - - __add__, __radd__ = _operator_fallbacks(_add, operator.add) - - def _sub(a, b): - """a - b""" - return Rational(a.numerator * b.denominator - - b.numerator * a.denominator, - a.denominator * b.denominator) - - __sub__, __rsub__ = _operator_fallbacks(_sub, operator.sub) - - def _mul(a, b): - """a * b""" - return Rational(a.numerator * b.numerator, a.denominator * b.denominator) - - __mul__, __rmul__ = _operator_fallbacks(_mul, operator.mul) - - def _div(a, b): - """a / b""" - return Rational(a.numerator * b.denominator, - a.denominator * b.numerator) - - __truediv__, __rtruediv__ = _operator_fallbacks(_div, operator.truediv) - __div__, __rdiv__ = _operator_fallbacks(_div, operator.div) - - def __floordiv__(a, b): - """a // b""" - # Will be math.floor(a / b) in 3.0. - div = a / b - if isinstance(div, RationalAbc): - # trunc(math.floor(div)) doesn't work if the rational is - # more precise than a float because the intermediate - # rounding may cross an integer boundary. - return div.numerator // div.denominator - else: - return math.floor(div) - - def __rfloordiv__(b, a): - """a // b""" - # Will be math.floor(a / b) in 3.0. - div = a / b - if isinstance(div, RationalAbc): - # trunc(math.floor(div)) doesn't work if the rational is - # more precise than a float because the intermediate - # rounding may cross an integer boundary. - return div.numerator // div.denominator - else: - return math.floor(div) - - def __mod__(a, b): - """a % b""" - div = a // b - return a - b * div - - def __rmod__(b, a): - """a % b""" - div = a // b - return a - b * div - - def __pow__(a, b): - """a ** b - - If b is not an integer, the result will be a float or complex - since roots are generally irrational. If b is an integer, the - result will be rational. - - """ - if isinstance(b, RationalAbc): - if b.denominator == 1: - power = b.numerator - if power >= 0: - return Rational(a.numerator ** power, - a.denominator ** power) - else: - return Rational(a.denominator ** -power, - a.numerator ** -power) - else: - # A fractional power will generally produce an - # irrational number. - return float(a) ** float(b) - else: - return float(a) ** b - - def __rpow__(b, a): - """a ** b""" - if b.denominator == 1 and b.numerator >= 0: - # If a is an int, keep it that way if possible. - return a ** b.numerator - - if isinstance(a, RationalAbc): - return Rational(a.numerator, a.denominator) ** b - - if b.denominator == 1: - return a ** b.numerator - - return a ** float(b) - - def __pos__(a): - """+a: Coerces a subclass instance to Rational""" - return Rational(a.numerator, a.denominator) - - def __neg__(a): - """-a""" - return Rational(-a.numerator, a.denominator) - - def __abs__(a): - """abs(a)""" - return Rational(abs(a.numerator), a.denominator) - - def __trunc__(a): - """trunc(a)""" - if a.numerator < 0: - return -(-a.numerator // a.denominator) - else: - return a.numerator // a.denominator - - def __hash__(self): - """hash(self) - - Tricky because values that are exactly representable as a - float must have the same hash as that float. - - """ - # XXX since this method is expensive, consider caching the result - if self.denominator == 1: - # Get integers right. - return hash(self.numerator) - # Expensive check, but definitely correct. - if self == float(self): - return hash(float(self)) - else: - # Use tuple's hash to avoid a high collision rate on - # simple fractions. - return hash((self.numerator, self.denominator)) - - def __eq__(a, b): - """a == b""" - if isinstance(b, RationalAbc): - return (a.numerator == b.numerator and - a.denominator == b.denominator) - if isinstance(b, numbers.Complex) and b.imag == 0: - b = b.real - if isinstance(b, float): - return a == a.from_float(b) - else: - # XXX: If b.__eq__ is implemented like this method, it may - # give the wrong answer after float(a) changes a's - # value. Better ways of doing this are welcome. - return float(a) == b - - def _subtractAndCompareToZero(a, b, op): - """Helper function for comparison operators. - - Subtracts b from a, exactly if possible, and compares the - result with 0 using op, in such a way that the comparison - won't recurse. If the difference raises a TypeError, returns - NotImplemented instead. - - """ - if isinstance(b, numbers.Complex) and b.imag == 0: - b = b.real - if isinstance(b, float): - b = a.from_float(b) - try: - # XXX: If b <: Real but not <: RationalAbc, this is likely - # to fall back to a float. If the actual values differ by - # less than MIN_FLOAT, this could falsely call them equal, - # which would make <= inconsistent with ==. Better ways of - # doing this are welcome. - diff = a - b - except TypeError: - return NotImplemented - if isinstance(diff, RationalAbc): - return op(diff.numerator, 0) - return op(diff, 0) - - def __lt__(a, b): - """a < b""" - return a._subtractAndCompareToZero(b, operator.lt) - - def __gt__(a, b): - """a > b""" - return a._subtractAndCompareToZero(b, operator.gt) - - def __le__(a, b): - """a <= b""" - return a._subtractAndCompareToZero(b, operator.le) - - def __ge__(a, b): - """a >= b""" - return a._subtractAndCompareToZero(b, operator.ge) - - def __nonzero__(a): - """a != 0""" - return a.numerator != 0 - - # support for pickling, copy, and deepcopy - - def __reduce__(self): - return (self.__class__, (str(self),)) - - def __copy__(self): - if type(self) == Rational: - return self # I'm immutable; therefore I am my own clone - return self.__class__(self.numerator, self.denominator) - - def __deepcopy__(self, memo): - if type(self) == Rational: - return self # My components are also immutable - return self.__class__(self.numerator, self.denominator) diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index 9612a4b..ddc5842 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -5,7 +5,7 @@ from test.test_support import fcmp, have_unicode, TESTFN, unlink, \ run_unittest, run_with_locale from operator import neg -import sys, warnings, cStringIO, random, rational, UserDict +import sys, warnings, cStringIO, random, fractions, UserDict warnings.filterwarnings("ignore", "hex../oct.. of negative int", FutureWarning, __name__) warnings.filterwarnings("ignore", "integer argument expected", @@ -703,7 +703,7 @@ class BuiltinTest(unittest.TestCase): n, d = f.as_integer_ratio() self.assertEqual(float(n).__truediv__(d), f) - R = rational.Rational + R = fractions.Fraction self.assertEqual(R(0, 1), R(*float(0.0).as_integer_ratio())) self.assertEqual(R(5, 2), diff --git a/Lib/test/test_fractions.py b/Lib/test/test_fractions.py new file mode 100644 index 0000000..cd35644 --- /dev/null +++ b/Lib/test/test_fractions.py @@ -0,0 +1,412 @@ +"""Tests for Lib/fractions.py.""" + +from decimal import Decimal +from test.test_support import run_unittest, verbose +import math +import operator +import fractions +import unittest +from copy import copy, deepcopy +from cPickle import dumps, loads +R = fractions.Fraction +gcd = fractions.gcd + + +class GcdTest(unittest.TestCase): + + def testMisc(self): + self.assertEquals(0, gcd(0, 0)) + self.assertEquals(1, gcd(1, 0)) + self.assertEquals(-1, gcd(-1, 0)) + self.assertEquals(1, gcd(0, 1)) + self.assertEquals(-1, gcd(0, -1)) + self.assertEquals(1, gcd(7, 1)) + self.assertEquals(-1, gcd(7, -1)) + self.assertEquals(1, gcd(-23, 15)) + self.assertEquals(12, gcd(120, 84)) + self.assertEquals(-12, gcd(84, -120)) + + +def _components(r): + return (r.numerator, r.denominator) + + +class FractionTest(unittest.TestCase): + + def assertTypedEquals(self, expected, actual): + """Asserts that both the types and values are the same.""" + self.assertEquals(type(expected), type(actual)) + self.assertEquals(expected, actual) + + def assertRaisesMessage(self, exc_type, message, + callable, *args, **kwargs): + """Asserts that callable(*args, **kwargs) raises exc_type(message).""" + try: + callable(*args, **kwargs) + except exc_type, e: + self.assertEquals(message, str(e)) + else: + self.fail("%s not raised" % exc_type.__name__) + + def testInit(self): + self.assertEquals((0, 1), _components(R())) + self.assertEquals((7, 1), _components(R(7))) + self.assertEquals((7, 3), _components(R(R(7, 3)))) + + self.assertEquals((-1, 1), _components(R(-1, 1))) + self.assertEquals((-1, 1), _components(R(1, -1))) + self.assertEquals((1, 1), _components(R(-2, -2))) + self.assertEquals((1, 2), _components(R(5, 10))) + self.assertEquals((7, 15), _components(R(7, 15))) + self.assertEquals((10**23, 1), _components(R(10**23))) + + self.assertRaisesMessage(ZeroDivisionError, "Fraction(12, 0)", + R, 12, 0) + self.assertRaises(TypeError, R, 1.5) + self.assertRaises(TypeError, R, 1.5 + 3j) + + self.assertRaises(TypeError, R, R(1, 2), 3) + self.assertRaises(TypeError, R, "3/2", 3) + + def testFromString(self): + self.assertEquals((5, 1), _components(R("5"))) + self.assertEquals((3, 2), _components(R("3/2"))) + self.assertEquals((3, 2), _components(R(" \n +3/2"))) + self.assertEquals((-3, 2), _components(R("-3/2 "))) + self.assertEquals((13, 2), _components(R(" 013/02 \n "))) + self.assertEquals((13, 2), _components(R(u" 013/02 \n "))) + + self.assertEquals((16, 5), _components(R(" 3.2 "))) + self.assertEquals((-16, 5), _components(R(u" -3.2 "))) + self.assertEquals((-3, 1), _components(R(u" -3. "))) + self.assertEquals((3, 5), _components(R(u" .6 "))) + + + self.assertRaisesMessage( + ZeroDivisionError, "Fraction(3, 0)", + R, "3/0") + self.assertRaisesMessage( + ValueError, "Invalid literal for Fraction: 3/", + R, "3/") + self.assertRaisesMessage( + ValueError, "Invalid literal for Fraction: 3 /2", + R, "3 /2") + self.assertRaisesMessage( + # Denominators don't need a sign. + ValueError, "Invalid literal for Fraction: 3/+2", + R, "3/+2") + self.assertRaisesMessage( + # Imitate float's parsing. + ValueError, "Invalid literal for Fraction: + 3/2", + R, "+ 3/2") + self.assertRaisesMessage( + # Avoid treating '.' as a regex special character. + ValueError, "Invalid literal for Fraction: 3a2", + R, "3a2") + self.assertRaisesMessage( + # Only parse ordinary decimals, not scientific form. + ValueError, "Invalid literal for Fraction: 3.2e4", + R, "3.2e4") + self.assertRaisesMessage( + # Don't accept combinations of decimals and fractions. + ValueError, "Invalid literal for Fraction: 3/7.2", + R, "3/7.2") + self.assertRaisesMessage( + # Don't accept combinations of decimals and fractions. + ValueError, "Invalid literal for Fraction: 3.2/7", + R, "3.2/7") + self.assertRaisesMessage( + # Allow 3. and .3, but not . + ValueError, "Invalid literal for Fraction: .", + R, ".") + + def testImmutable(self): + r = R(7, 3) + r.__init__(2, 15) + self.assertEquals((7, 3), _components(r)) + + self.assertRaises(AttributeError, setattr, r, 'numerator', 12) + self.assertRaises(AttributeError, setattr, r, 'denominator', 6) + self.assertEquals((7, 3), _components(r)) + + # But if you _really_ need to: + r._numerator = 4 + r._denominator = 2 + self.assertEquals((4, 2), _components(r)) + # Which breaks some important operations: + self.assertNotEquals(R(4, 2), r) + + def testFromFloat(self): + self.assertRaisesMessage( + TypeError, "Fraction.from_float() only takes floats, not 3 (int)", + R.from_float, 3) + + self.assertEquals((0, 1), _components(R.from_float(-0.0))) + self.assertEquals((10, 1), _components(R.from_float(10.0))) + self.assertEquals((-5, 2), _components(R.from_float(-2.5))) + self.assertEquals((99999999999999991611392, 1), + _components(R.from_float(1e23))) + self.assertEquals(float(10**23), float(R.from_float(1e23))) + self.assertEquals((3602879701896397, 1125899906842624), + _components(R.from_float(3.2))) + self.assertEquals(3.2, float(R.from_float(3.2))) + + inf = 1e1000 + nan = inf - inf + self.assertRaisesMessage( + TypeError, "Cannot convert inf to Fraction.", + R.from_float, inf) + self.assertRaisesMessage( + TypeError, "Cannot convert -inf to Fraction.", + R.from_float, -inf) + self.assertRaisesMessage( + TypeError, "Cannot convert nan to Fraction.", + R.from_float, nan) + + def testFromDecimal(self): + self.assertRaisesMessage( + TypeError, + "Fraction.from_decimal() only takes Decimals, not 3 (int)", + R.from_decimal, 3) + self.assertEquals(R(0), R.from_decimal(Decimal("-0"))) + self.assertEquals(R(5, 10), R.from_decimal(Decimal("0.5"))) + self.assertEquals(R(5, 1000), R.from_decimal(Decimal("5e-3"))) + self.assertEquals(R(5000), R.from_decimal(Decimal("5e3"))) + self.assertEquals(1 - R(1, 10**30), + R.from_decimal(Decimal("0." + "9" * 30))) + + self.assertRaisesMessage( + TypeError, "Cannot convert Infinity to Fraction.", + R.from_decimal, Decimal("inf")) + self.assertRaisesMessage( + TypeError, "Cannot convert -Infinity to Fraction.", + R.from_decimal, Decimal("-inf")) + self.assertRaisesMessage( + TypeError, "Cannot convert NaN to Fraction.", + R.from_decimal, Decimal("nan")) + self.assertRaisesMessage( + TypeError, "Cannot convert sNaN to Fraction.", + R.from_decimal, Decimal("snan")) + + def testFromContinuedFraction(self): + self.assertRaises(TypeError, R.from_continued_fraction, None) + phi = R.from_continued_fraction([1]*100) + self.assertEquals(round(phi - (1 + 5 ** 0.5) / 2, 10), 0.0) + + minusphi = R.from_continued_fraction([-1]*100) + self.assertEquals(round(minusphi + (1 + 5 ** 0.5) / 2, 10), 0.0) + + self.assertEquals(R.from_continued_fraction([0]), R(0)) + self.assertEquals(R.from_continued_fraction([]), R(0)) + + def testAsContinuedFraction(self): + self.assertEqual(R.from_float(math.pi).as_continued_fraction()[:15], + [3, 7, 15, 1, 292, 1, 1, 1, 2, 1, 3, 1, 14, 3, 3]) + self.assertEqual(R.from_float(-math.pi).as_continued_fraction()[:16], + [-4, 1, 6, 15, 1, 292, 1, 1, 1, 2, 1, 3, 1, 14, 3, 3]) + self.assertEqual(R(0).as_continued_fraction(), [0]) + + def testApproximateFrom(self): + self.assertEqual(R.from_float(math.pi).approximate(10000), R(355, 113)) + self.assertEqual(R.from_float(-math.pi).approximate(10000), R(-355, 113)) + self.assertEqual(R.from_float(0.0).approximate(10000), R(0)) + + def testConversions(self): + self.assertTypedEquals(-1, math.trunc(R(-11, 10))) + self.assertTypedEquals(-1, int(R(-11, 10))) + + self.assertEquals(False, bool(R(0, 1))) + self.assertEquals(True, bool(R(3, 2))) + self.assertTypedEquals(0.1, float(R(1, 10))) + + # Check that __float__ isn't implemented by converting the + # numerator and denominator to float before dividing. + self.assertRaises(OverflowError, float, long('2'*400+'7')) + self.assertAlmostEquals(2.0/3, + float(R(long('2'*400+'7'), long('3'*400+'1')))) + + self.assertTypedEquals(0.1+0j, complex(R(1,10))) + + + def testArithmetic(self): + self.assertEquals(R(1, 2), R(1, 10) + R(2, 5)) + self.assertEquals(R(-3, 10), R(1, 10) - R(2, 5)) + self.assertEquals(R(1, 25), R(1, 10) * R(2, 5)) + self.assertEquals(R(1, 4), R(1, 10) / R(2, 5)) + self.assertTypedEquals(2, R(9, 10) // R(2, 5)) + self.assertTypedEquals(10**23, R(10**23, 1) // R(1)) + self.assertEquals(R(2, 3), R(-7, 3) % R(3, 2)) + self.assertEquals(R(8, 27), R(2, 3) ** R(3)) + self.assertEquals(R(27, 8), R(2, 3) ** R(-3)) + self.assertTypedEquals(2.0, R(4) ** R(1, 2)) + # Will return 1j in 3.0: + self.assertRaises(ValueError, pow, R(-1), R(1, 2)) + + def testMixedArithmetic(self): + self.assertTypedEquals(R(11, 10), R(1, 10) + 1) + self.assertTypedEquals(1.1, R(1, 10) + 1.0) + self.assertTypedEquals(1.1 + 0j, R(1, 10) + (1.0 + 0j)) + self.assertTypedEquals(R(11, 10), 1 + R(1, 10)) + self.assertTypedEquals(1.1, 1.0 + R(1, 10)) + self.assertTypedEquals(1.1 + 0j, (1.0 + 0j) + R(1, 10)) + + self.assertTypedEquals(R(-9, 10), R(1, 10) - 1) + self.assertTypedEquals(-0.9, R(1, 10) - 1.0) + self.assertTypedEquals(-0.9 + 0j, R(1, 10) - (1.0 + 0j)) + self.assertTypedEquals(R(9, 10), 1 - R(1, 10)) + self.assertTypedEquals(0.9, 1.0 - R(1, 10)) + self.assertTypedEquals(0.9 + 0j, (1.0 + 0j) - R(1, 10)) + + self.assertTypedEquals(R(1, 10), R(1, 10) * 1) + self.assertTypedEquals(0.1, R(1, 10) * 1.0) + self.assertTypedEquals(0.1 + 0j, R(1, 10) * (1.0 + 0j)) + self.assertTypedEquals(R(1, 10), 1 * R(1, 10)) + self.assertTypedEquals(0.1, 1.0 * R(1, 10)) + self.assertTypedEquals(0.1 + 0j, (1.0 + 0j) * R(1, 10)) + + self.assertTypedEquals(R(1, 10), R(1, 10) / 1) + self.assertTypedEquals(0.1, R(1, 10) / 1.0) + self.assertTypedEquals(0.1 + 0j, R(1, 10) / (1.0 + 0j)) + self.assertTypedEquals(R(10, 1), 1 / R(1, 10)) + self.assertTypedEquals(10.0, 1.0 / R(1, 10)) + self.assertTypedEquals(10.0 + 0j, (1.0 + 0j) / R(1, 10)) + + self.assertTypedEquals(0, R(1, 10) // 1) + self.assertTypedEquals(0.0, R(1, 10) // 1.0) + self.assertTypedEquals(10, 1 // R(1, 10)) + self.assertTypedEquals(10**23, 10**22 // R(1, 10)) + self.assertTypedEquals(10.0, 1.0 // R(1, 10)) + + self.assertTypedEquals(R(1, 10), R(1, 10) % 1) + self.assertTypedEquals(0.1, R(1, 10) % 1.0) + self.assertTypedEquals(R(0, 1), 1 % R(1, 10)) + self.assertTypedEquals(0.0, 1.0 % R(1, 10)) + + # No need for divmod since we don't override it. + + # ** has more interesting conversion rules. + self.assertTypedEquals(R(100, 1), R(1, 10) ** -2) + self.assertTypedEquals(R(100, 1), R(10, 1) ** 2) + self.assertTypedEquals(0.1, R(1, 10) ** 1.0) + self.assertTypedEquals(0.1 + 0j, R(1, 10) ** (1.0 + 0j)) + self.assertTypedEquals(4 , 2 ** R(2, 1)) + # Will return 1j in 3.0: + self.assertRaises(ValueError, pow, (-1), R(1, 2)) + self.assertTypedEquals(R(1, 4) , 2 ** R(-2, 1)) + self.assertTypedEquals(2.0 , 4 ** R(1, 2)) + self.assertTypedEquals(0.25, 2.0 ** R(-2, 1)) + self.assertTypedEquals(1.0 + 0j, (1.0 + 0j) ** R(1, 10)) + + def testMixingWithDecimal(self): + # Decimal refuses mixed comparisons. + self.assertRaisesMessage( + TypeError, + "unsupported operand type(s) for +: 'Fraction' and 'Decimal'", + operator.add, R(3,11), Decimal('3.1415926')) + self.assertNotEquals(R(5, 2), Decimal('2.5')) + + def testComparisons(self): + self.assertTrue(R(1, 2) < R(2, 3)) + self.assertFalse(R(1, 2) < R(1, 2)) + self.assertTrue(R(1, 2) <= R(2, 3)) + self.assertTrue(R(1, 2) <= R(1, 2)) + self.assertFalse(R(2, 3) <= R(1, 2)) + self.assertTrue(R(1, 2) == R(1, 2)) + self.assertFalse(R(1, 2) == R(1, 3)) + self.assertFalse(R(1, 2) != R(1, 2)) + self.assertTrue(R(1, 2) != R(1, 3)) + + def testMixedLess(self): + self.assertTrue(2 < R(5, 2)) + self.assertFalse(2 < R(4, 2)) + self.assertTrue(R(5, 2) < 3) + self.assertFalse(R(4, 2) < 2) + + self.assertTrue(R(1, 2) < 0.6) + self.assertFalse(R(1, 2) < 0.4) + self.assertTrue(0.4 < R(1, 2)) + self.assertFalse(0.5 < R(1, 2)) + + def testMixedLessEqual(self): + self.assertTrue(0.5 <= R(1, 2)) + self.assertFalse(0.6 <= R(1, 2)) + self.assertTrue(R(1, 2) <= 0.5) + self.assertFalse(R(1, 2) <= 0.4) + self.assertTrue(2 <= R(4, 2)) + self.assertFalse(2 <= R(3, 2)) + self.assertTrue(R(4, 2) <= 2) + self.assertFalse(R(5, 2) <= 2) + + def testBigFloatComparisons(self): + # Because 10**23 can't be represented exactly as a float: + self.assertFalse(R(10**23) == float(10**23)) + # The first test demonstrates why these are important. + self.assertFalse(1e23 < float(R(math.trunc(1e23) + 1))) + self.assertTrue(1e23 < R(math.trunc(1e23) + 1)) + self.assertFalse(1e23 <= R(math.trunc(1e23) - 1)) + self.assertTrue(1e23 > R(math.trunc(1e23) - 1)) + self.assertFalse(1e23 >= R(math.trunc(1e23) + 1)) + + def testBigComplexComparisons(self): + self.assertFalse(R(10**23) == complex(10**23)) + self.assertTrue(R(10**23) > complex(10**23)) + self.assertFalse(R(10**23) <= complex(10**23)) + + def testMixedEqual(self): + self.assertTrue(0.5 == R(1, 2)) + self.assertFalse(0.6 == R(1, 2)) + self.assertTrue(R(1, 2) == 0.5) + self.assertFalse(R(1, 2) == 0.4) + self.assertTrue(2 == R(4, 2)) + self.assertFalse(2 == R(3, 2)) + self.assertTrue(R(4, 2) == 2) + self.assertFalse(R(5, 2) == 2) + + def testStringification(self): + self.assertEquals("Fraction(7,3)", repr(R(7, 3))) + self.assertEquals("7/3", str(R(7, 3))) + self.assertEquals("7", str(R(7, 1))) + + def testHash(self): + self.assertEquals(hash(2.5), hash(R(5, 2))) + self.assertEquals(hash(10**50), hash(R(10**50))) + self.assertNotEquals(hash(float(10**23)), hash(R(10**23))) + + def testApproximatePi(self): + # Algorithm borrowed from + # http://docs.python.org/lib/decimal-recipes.html + three = R(3) + lasts, t, s, n, na, d, da = 0, three, 3, 1, 0, 0, 24 + while abs(s - lasts) > R(1, 10**9): + lasts = s + n, na = n+na, na+8 + d, da = d+da, da+32 + t = (t * n) / d + s += t + self.assertAlmostEquals(math.pi, s) + + def testApproximateCos1(self): + # Algorithm borrowed from + # http://docs.python.org/lib/decimal-recipes.html + x = R(1) + i, lasts, s, fact, num, sign = 0, 0, R(1), 1, 1, 1 + while abs(s - lasts) > R(1, 10**9): + lasts = s + i += 2 + fact *= i * (i-1) + num *= x * x + sign *= -1 + s += num / fact * sign + self.assertAlmostEquals(math.cos(1), s) + + def test_copy_deepcopy_pickle(self): + r = R(13, 7) + self.assertEqual(r, loads(dumps(r))) + self.assertEqual(id(r), id(copy(r))) + self.assertEqual(id(r), id(deepcopy(r))) + +def test_main(): + run_unittest(FractionTest, GcdTest) + +if __name__ == '__main__': + test_main() diff --git a/Lib/test/test_rational.py b/Lib/test/test_rational.py deleted file mode 100644 index 8e62081..0000000 --- a/Lib/test/test_rational.py +++ /dev/null @@ -1,412 +0,0 @@ -"""Tests for Lib/rational.py.""" - -from decimal import Decimal -from test.test_support import run_unittest, verbose -import math -import operator -import rational -import unittest -from copy import copy, deepcopy -from cPickle import dumps, loads -R = rational.Rational -gcd = rational.gcd - - -class GcdTest(unittest.TestCase): - - def testMisc(self): - self.assertEquals(0, gcd(0, 0)) - self.assertEquals(1, gcd(1, 0)) - self.assertEquals(-1, gcd(-1, 0)) - self.assertEquals(1, gcd(0, 1)) - self.assertEquals(-1, gcd(0, -1)) - self.assertEquals(1, gcd(7, 1)) - self.assertEquals(-1, gcd(7, -1)) - self.assertEquals(1, gcd(-23, 15)) - self.assertEquals(12, gcd(120, 84)) - self.assertEquals(-12, gcd(84, -120)) - - -def _components(r): - return (r.numerator, r.denominator) - - -class RationalTest(unittest.TestCase): - - def assertTypedEquals(self, expected, actual): - """Asserts that both the types and values are the same.""" - self.assertEquals(type(expected), type(actual)) - self.assertEquals(expected, actual) - - def assertRaisesMessage(self, exc_type, message, - callable, *args, **kwargs): - """Asserts that callable(*args, **kwargs) raises exc_type(message).""" - try: - callable(*args, **kwargs) - except exc_type, e: - self.assertEquals(message, str(e)) - else: - self.fail("%s not raised" % exc_type.__name__) - - def testInit(self): - self.assertEquals((0, 1), _components(R())) - self.assertEquals((7, 1), _components(R(7))) - self.assertEquals((7, 3), _components(R(R(7, 3)))) - - self.assertEquals((-1, 1), _components(R(-1, 1))) - self.assertEquals((-1, 1), _components(R(1, -1))) - self.assertEquals((1, 1), _components(R(-2, -2))) - self.assertEquals((1, 2), _components(R(5, 10))) - self.assertEquals((7, 15), _components(R(7, 15))) - self.assertEquals((10**23, 1), _components(R(10**23))) - - self.assertRaisesMessage(ZeroDivisionError, "Rational(12, 0)", - R, 12, 0) - self.assertRaises(TypeError, R, 1.5) - self.assertRaises(TypeError, R, 1.5 + 3j) - - self.assertRaises(TypeError, R, R(1, 2), 3) - self.assertRaises(TypeError, R, "3/2", 3) - - def testFromString(self): - self.assertEquals((5, 1), _components(R("5"))) - self.assertEquals((3, 2), _components(R("3/2"))) - self.assertEquals((3, 2), _components(R(" \n +3/2"))) - self.assertEquals((-3, 2), _components(R("-3/2 "))) - self.assertEquals((13, 2), _components(R(" 013/02 \n "))) - self.assertEquals((13, 2), _components(R(u" 013/02 \n "))) - - self.assertEquals((16, 5), _components(R(" 3.2 "))) - self.assertEquals((-16, 5), _components(R(u" -3.2 "))) - self.assertEquals((-3, 1), _components(R(u" -3. "))) - self.assertEquals((3, 5), _components(R(u" .6 "))) - - - self.assertRaisesMessage( - ZeroDivisionError, "Rational(3, 0)", - R, "3/0") - self.assertRaisesMessage( - ValueError, "Invalid literal for Rational: 3/", - R, "3/") - self.assertRaisesMessage( - ValueError, "Invalid literal for Rational: 3 /2", - R, "3 /2") - self.assertRaisesMessage( - # Denominators don't need a sign. - ValueError, "Invalid literal for Rational: 3/+2", - R, "3/+2") - self.assertRaisesMessage( - # Imitate float's parsing. - ValueError, "Invalid literal for Rational: + 3/2", - R, "+ 3/2") - self.assertRaisesMessage( - # Avoid treating '.' as a regex special character. - ValueError, "Invalid literal for Rational: 3a2", - R, "3a2") - self.assertRaisesMessage( - # Only parse ordinary decimals, not scientific form. - ValueError, "Invalid literal for Rational: 3.2e4", - R, "3.2e4") - self.assertRaisesMessage( - # Don't accept combinations of decimals and rationals. - ValueError, "Invalid literal for Rational: 3/7.2", - R, "3/7.2") - self.assertRaisesMessage( - # Don't accept combinations of decimals and rationals. - ValueError, "Invalid literal for Rational: 3.2/7", - R, "3.2/7") - self.assertRaisesMessage( - # Allow 3. and .3, but not . - ValueError, "Invalid literal for Rational: .", - R, ".") - - def testImmutable(self): - r = R(7, 3) - r.__init__(2, 15) - self.assertEquals((7, 3), _components(r)) - - self.assertRaises(AttributeError, setattr, r, 'numerator', 12) - self.assertRaises(AttributeError, setattr, r, 'denominator', 6) - self.assertEquals((7, 3), _components(r)) - - # But if you _really_ need to: - r._numerator = 4 - r._denominator = 2 - self.assertEquals((4, 2), _components(r)) - # Which breaks some important operations: - self.assertNotEquals(R(4, 2), r) - - def testFromFloat(self): - self.assertRaisesMessage( - TypeError, "Rational.from_float() only takes floats, not 3 (int)", - R.from_float, 3) - - self.assertEquals((0, 1), _components(R.from_float(-0.0))) - self.assertEquals((10, 1), _components(R.from_float(10.0))) - self.assertEquals((-5, 2), _components(R.from_float(-2.5))) - self.assertEquals((99999999999999991611392, 1), - _components(R.from_float(1e23))) - self.assertEquals(float(10**23), float(R.from_float(1e23))) - self.assertEquals((3602879701896397, 1125899906842624), - _components(R.from_float(3.2))) - self.assertEquals(3.2, float(R.from_float(3.2))) - - inf = 1e1000 - nan = inf - inf - self.assertRaisesMessage( - TypeError, "Cannot convert inf to Rational.", - R.from_float, inf) - self.assertRaisesMessage( - TypeError, "Cannot convert -inf to Rational.", - R.from_float, -inf) - self.assertRaisesMessage( - TypeError, "Cannot convert nan to Rational.", - R.from_float, nan) - - def testFromDecimal(self): - self.assertRaisesMessage( - TypeError, - "Rational.from_decimal() only takes Decimals, not 3 (int)", - R.from_decimal, 3) - self.assertEquals(R(0), R.from_decimal(Decimal("-0"))) - self.assertEquals(R(5, 10), R.from_decimal(Decimal("0.5"))) - self.assertEquals(R(5, 1000), R.from_decimal(Decimal("5e-3"))) - self.assertEquals(R(5000), R.from_decimal(Decimal("5e3"))) - self.assertEquals(1 - R(1, 10**30), - R.from_decimal(Decimal("0." + "9" * 30))) - - self.assertRaisesMessage( - TypeError, "Cannot convert Infinity to Rational.", - R.from_decimal, Decimal("inf")) - self.assertRaisesMessage( - TypeError, "Cannot convert -Infinity to Rational.", - R.from_decimal, Decimal("-inf")) - self.assertRaisesMessage( - TypeError, "Cannot convert NaN to Rational.", - R.from_decimal, Decimal("nan")) - self.assertRaisesMessage( - TypeError, "Cannot convert sNaN to Rational.", - R.from_decimal, Decimal("snan")) - - def testFromContinuedFraction(self): - self.assertRaises(TypeError, R.from_continued_fraction, None) - phi = R.from_continued_fraction([1]*100) - self.assertEquals(round(phi - (1 + 5 ** 0.5) / 2, 10), 0.0) - - minusphi = R.from_continued_fraction([-1]*100) - self.assertEquals(round(minusphi + (1 + 5 ** 0.5) / 2, 10), 0.0) - - self.assertEquals(R.from_continued_fraction([0]), R(0)) - self.assertEquals(R.from_continued_fraction([]), R(0)) - - def testAsContinuedFraction(self): - self.assertEqual(R.from_float(math.pi).as_continued_fraction()[:15], - [3, 7, 15, 1, 292, 1, 1, 1, 2, 1, 3, 1, 14, 3, 3]) - self.assertEqual(R.from_float(-math.pi).as_continued_fraction()[:16], - [-4, 1, 6, 15, 1, 292, 1, 1, 1, 2, 1, 3, 1, 14, 3, 3]) - self.assertEqual(R(0).as_continued_fraction(), [0]) - - def testApproximateFrom(self): - self.assertEqual(R.from_float(math.pi).approximate(10000), R(355, 113)) - self.assertEqual(R.from_float(-math.pi).approximate(10000), R(-355, 113)) - self.assertEqual(R.from_float(0.0).approximate(10000), R(0)) - - def testConversions(self): - self.assertTypedEquals(-1, math.trunc(R(-11, 10))) - self.assertTypedEquals(-1, int(R(-11, 10))) - - self.assertEquals(False, bool(R(0, 1))) - self.assertEquals(True, bool(R(3, 2))) - self.assertTypedEquals(0.1, float(R(1, 10))) - - # Check that __float__ isn't implemented by converting the - # numerator and denominator to float before dividing. - self.assertRaises(OverflowError, float, long('2'*400+'7')) - self.assertAlmostEquals(2.0/3, - float(R(long('2'*400+'7'), long('3'*400+'1')))) - - self.assertTypedEquals(0.1+0j, complex(R(1,10))) - - - def testArithmetic(self): - self.assertEquals(R(1, 2), R(1, 10) + R(2, 5)) - self.assertEquals(R(-3, 10), R(1, 10) - R(2, 5)) - self.assertEquals(R(1, 25), R(1, 10) * R(2, 5)) - self.assertEquals(R(1, 4), R(1, 10) / R(2, 5)) - self.assertTypedEquals(2, R(9, 10) // R(2, 5)) - self.assertTypedEquals(10**23, R(10**23, 1) // R(1)) - self.assertEquals(R(2, 3), R(-7, 3) % R(3, 2)) - self.assertEquals(R(8, 27), R(2, 3) ** R(3)) - self.assertEquals(R(27, 8), R(2, 3) ** R(-3)) - self.assertTypedEquals(2.0, R(4) ** R(1, 2)) - # Will return 1j in 3.0: - self.assertRaises(ValueError, pow, R(-1), R(1, 2)) - - def testMixedArithmetic(self): - self.assertTypedEquals(R(11, 10), R(1, 10) + 1) - self.assertTypedEquals(1.1, R(1, 10) + 1.0) - self.assertTypedEquals(1.1 + 0j, R(1, 10) + (1.0 + 0j)) - self.assertTypedEquals(R(11, 10), 1 + R(1, 10)) - self.assertTypedEquals(1.1, 1.0 + R(1, 10)) - self.assertTypedEquals(1.1 + 0j, (1.0 + 0j) + R(1, 10)) - - self.assertTypedEquals(R(-9, 10), R(1, 10) - 1) - self.assertTypedEquals(-0.9, R(1, 10) - 1.0) - self.assertTypedEquals(-0.9 + 0j, R(1, 10) - (1.0 + 0j)) - self.assertTypedEquals(R(9, 10), 1 - R(1, 10)) - self.assertTypedEquals(0.9, 1.0 - R(1, 10)) - self.assertTypedEquals(0.9 + 0j, (1.0 + 0j) - R(1, 10)) - - self.assertTypedEquals(R(1, 10), R(1, 10) * 1) - self.assertTypedEquals(0.1, R(1, 10) * 1.0) - self.assertTypedEquals(0.1 + 0j, R(1, 10) * (1.0 + 0j)) - self.assertTypedEquals(R(1, 10), 1 * R(1, 10)) - self.assertTypedEquals(0.1, 1.0 * R(1, 10)) - self.assertTypedEquals(0.1 + 0j, (1.0 + 0j) * R(1, 10)) - - self.assertTypedEquals(R(1, 10), R(1, 10) / 1) - self.assertTypedEquals(0.1, R(1, 10) / 1.0) - self.assertTypedEquals(0.1 + 0j, R(1, 10) / (1.0 + 0j)) - self.assertTypedEquals(R(10, 1), 1 / R(1, 10)) - self.assertTypedEquals(10.0, 1.0 / R(1, 10)) - self.assertTypedEquals(10.0 + 0j, (1.0 + 0j) / R(1, 10)) - - self.assertTypedEquals(0, R(1, 10) // 1) - self.assertTypedEquals(0.0, R(1, 10) // 1.0) - self.assertTypedEquals(10, 1 // R(1, 10)) - self.assertTypedEquals(10**23, 10**22 // R(1, 10)) - self.assertTypedEquals(10.0, 1.0 // R(1, 10)) - - self.assertTypedEquals(R(1, 10), R(1, 10) % 1) - self.assertTypedEquals(0.1, R(1, 10) % 1.0) - self.assertTypedEquals(R(0, 1), 1 % R(1, 10)) - self.assertTypedEquals(0.0, 1.0 % R(1, 10)) - - # No need for divmod since we don't override it. - - # ** has more interesting conversion rules. - self.assertTypedEquals(R(100, 1), R(1, 10) ** -2) - self.assertTypedEquals(R(100, 1), R(10, 1) ** 2) - self.assertTypedEquals(0.1, R(1, 10) ** 1.0) - self.assertTypedEquals(0.1 + 0j, R(1, 10) ** (1.0 + 0j)) - self.assertTypedEquals(4 , 2 ** R(2, 1)) - # Will return 1j in 3.0: - self.assertRaises(ValueError, pow, (-1), R(1, 2)) - self.assertTypedEquals(R(1, 4) , 2 ** R(-2, 1)) - self.assertTypedEquals(2.0 , 4 ** R(1, 2)) - self.assertTypedEquals(0.25, 2.0 ** R(-2, 1)) - self.assertTypedEquals(1.0 + 0j, (1.0 + 0j) ** R(1, 10)) - - def testMixingWithDecimal(self): - # Decimal refuses mixed comparisons. - self.assertRaisesMessage( - TypeError, - "unsupported operand type(s) for +: 'Rational' and 'Decimal'", - operator.add, R(3,11), Decimal('3.1415926')) - self.assertNotEquals(R(5, 2), Decimal('2.5')) - - def testComparisons(self): - self.assertTrue(R(1, 2) < R(2, 3)) - self.assertFalse(R(1, 2) < R(1, 2)) - self.assertTrue(R(1, 2) <= R(2, 3)) - self.assertTrue(R(1, 2) <= R(1, 2)) - self.assertFalse(R(2, 3) <= R(1, 2)) - self.assertTrue(R(1, 2) == R(1, 2)) - self.assertFalse(R(1, 2) == R(1, 3)) - self.assertFalse(R(1, 2) != R(1, 2)) - self.assertTrue(R(1, 2) != R(1, 3)) - - def testMixedLess(self): - self.assertTrue(2 < R(5, 2)) - self.assertFalse(2 < R(4, 2)) - self.assertTrue(R(5, 2) < 3) - self.assertFalse(R(4, 2) < 2) - - self.assertTrue(R(1, 2) < 0.6) - self.assertFalse(R(1, 2) < 0.4) - self.assertTrue(0.4 < R(1, 2)) - self.assertFalse(0.5 < R(1, 2)) - - def testMixedLessEqual(self): - self.assertTrue(0.5 <= R(1, 2)) - self.assertFalse(0.6 <= R(1, 2)) - self.assertTrue(R(1, 2) <= 0.5) - self.assertFalse(R(1, 2) <= 0.4) - self.assertTrue(2 <= R(4, 2)) - self.assertFalse(2 <= R(3, 2)) - self.assertTrue(R(4, 2) <= 2) - self.assertFalse(R(5, 2) <= 2) - - def testBigFloatComparisons(self): - # Because 10**23 can't be represented exactly as a float: - self.assertFalse(R(10**23) == float(10**23)) - # The first test demonstrates why these are important. - self.assertFalse(1e23 < float(R(math.trunc(1e23) + 1))) - self.assertTrue(1e23 < R(math.trunc(1e23) + 1)) - self.assertFalse(1e23 <= R(math.trunc(1e23) - 1)) - self.assertTrue(1e23 > R(math.trunc(1e23) - 1)) - self.assertFalse(1e23 >= R(math.trunc(1e23) + 1)) - - def testBigComplexComparisons(self): - self.assertFalse(R(10**23) == complex(10**23)) - self.assertTrue(R(10**23) > complex(10**23)) - self.assertFalse(R(10**23) <= complex(10**23)) - - def testMixedEqual(self): - self.assertTrue(0.5 == R(1, 2)) - self.assertFalse(0.6 == R(1, 2)) - self.assertTrue(R(1, 2) == 0.5) - self.assertFalse(R(1, 2) == 0.4) - self.assertTrue(2 == R(4, 2)) - self.assertFalse(2 == R(3, 2)) - self.assertTrue(R(4, 2) == 2) - self.assertFalse(R(5, 2) == 2) - - def testStringification(self): - self.assertEquals("Rational(7,3)", repr(R(7, 3))) - self.assertEquals("7/3", str(R(7, 3))) - self.assertEquals("7", str(R(7, 1))) - - def testHash(self): - self.assertEquals(hash(2.5), hash(R(5, 2))) - self.assertEquals(hash(10**50), hash(R(10**50))) - self.assertNotEquals(hash(float(10**23)), hash(R(10**23))) - - def testApproximatePi(self): - # Algorithm borrowed from - # http://docs.python.org/lib/decimal-recipes.html - three = R(3) - lasts, t, s, n, na, d, da = 0, three, 3, 1, 0, 0, 24 - while abs(s - lasts) > R(1, 10**9): - lasts = s - n, na = n+na, na+8 - d, da = d+da, da+32 - t = (t * n) / d - s += t - self.assertAlmostEquals(math.pi, s) - - def testApproximateCos1(self): - # Algorithm borrowed from - # http://docs.python.org/lib/decimal-recipes.html - x = R(1) - i, lasts, s, fact, num, sign = 0, 0, R(1), 1, 1, 1 - while abs(s - lasts) > R(1, 10**9): - lasts = s - i += 2 - fact *= i * (i-1) - num *= x * x - sign *= -1 - s += num / fact * sign - self.assertAlmostEquals(math.cos(1), s) - - def test_copy_deepcopy_pickle(self): - r = R(13, 7) - self.assertEqual(r, loads(dumps(r))) - self.assertEqual(id(r), id(copy(r))) - self.assertEqual(id(r), id(deepcopy(r))) - -def test_main(): - run_unittest(RationalTest, GcdTest) - -if __name__ == '__main__': - test_main() diff --git a/Misc/NEWS b/Misc/NEWS index c519ba6..81eef0e 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -400,6 +400,10 @@ Core and builtins Library ------- +- Rename rational.py to fractions.py and the rational.Rational class + to fractions.Fraction, to avoid the name clash with the abstract + base class numbers.Rational. See discussion in issue #1682. + - The pickletools module now provides an optimize() function that eliminates unused PUT opcodes from a pickle string. -- cgit v0.12