summaryrefslogtreecommitdiffstats
path: root/Lib/fractions.py
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/fractions.py')
-rw-r--r--Lib/fractions.py272
1 files changed, 106 insertions, 166 deletions
diff --git a/Lib/fractions.py b/Lib/fractions.py
index 2e7047a..a0d86a4 100644
--- a/Lib/fractions.py
+++ b/Lib/fractions.py
@@ -1,17 +1,18 @@
# Originally contributed by Sjoerd Mullender.
# Significantly modified by Jeffrey Yasskin <jyasskin at gmail.com>.
-"""Fraction, infinite-precision, real numbers."""
+"""Rational, infinite-precision, real numbers."""
+from __future__ import division
from decimal import Decimal
import math
import numbers
import operator
import re
-import sys
__all__ = ['Fraction', 'gcd']
+Rational = numbers.Rational
def gcd(a, b):
@@ -20,27 +21,10 @@ def gcd(a, 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).
"""
- import warnings
- warnings.warn('fractions.gcd() is deprecated. Use math.gcd() instead.',
- DeprecationWarning, 2)
- if type(a) is int is type(b):
- if (b or a) < 0:
- return -math.gcd(a, b)
- return math.gcd(a, b)
- return _gcd(a, b)
-
-def _gcd(a, b):
- # Supports non-integers for backward compatibility.
while b:
a, b = b, a%b
return a
-# Constants related to the hash implementation; hash(x) is based
-# on the reduction of x modulo the prime _PyHASH_MODULUS.
-_PyHASH_MODULUS = sys.hash_info.modulus
-# Value to be used for rationals that reduce to infinity modulo
-# _PyHASH_MODULUS.
-_PyHASH_INF = sys.hash_info.inf
_RATIONAL_FORMAT = re.compile(r"""
\A\s* # optional whitespace at the start, then
@@ -57,7 +41,7 @@ _RATIONAL_FORMAT = re.compile(r"""
""", re.VERBOSE | re.IGNORECASE)
-class Fraction(numbers.Rational):
+class Fraction(Rational):
"""This class implements rational numbers.
In the two-argument form of the constructor, Fraction(8, 6) will
@@ -81,8 +65,8 @@ class Fraction(numbers.Rational):
__slots__ = ('_numerator', '_denominator')
# We're immutable, so use __new__ not __init__
- def __new__(cls, numerator=0, denominator=None, *, _normalize=True):
- """Constructs a Rational.
+ def __new__(cls, numerator=0, denominator=None):
+ """Constructs a Fraction.
Takes a string like '3/2' or '1.5', another Rational instance, a
numerator/denominator pair, or a float.
@@ -115,22 +99,25 @@ class Fraction(numbers.Rational):
self = super(Fraction, cls).__new__(cls)
if denominator is None:
- if type(numerator) is int:
- self._numerator = numerator
- self._denominator = 1
- return self
-
- elif isinstance(numerator, numbers.Rational):
+ if isinstance(numerator, Rational):
self._numerator = numerator.numerator
self._denominator = numerator.denominator
return self
- elif isinstance(numerator, (float, Decimal)):
- # Exact conversion
- self._numerator, self._denominator = numerator.as_integer_ratio()
+ elif isinstance(numerator, float):
+ # Exact conversion from float
+ value = Fraction.from_float(numerator)
+ self._numerator = value._numerator
+ self._denominator = value._denominator
+ return self
+
+ elif isinstance(numerator, Decimal):
+ value = Fraction.from_decimal(numerator)
+ self._numerator = value._numerator
+ self._denominator = value._denominator
return self
- elif isinstance(numerator, str):
+ elif isinstance(numerator, basestring):
# Handle construction from strings.
m = _RATIONAL_FORMAT.match(numerator)
if m is None:
@@ -161,11 +148,8 @@ class Fraction(numbers.Rational):
raise TypeError("argument should be a string "
"or a Rational instance")
- elif type(numerator) is int is type(denominator):
- pass # *very* normal case
-
- elif (isinstance(numerator, numbers.Rational) and
- isinstance(denominator, numbers.Rational)):
+ elif (isinstance(numerator, Rational) and
+ isinstance(denominator, Rational)):
numerator, denominator = (
numerator.numerator * denominator.denominator,
denominator.numerator * numerator.denominator
@@ -176,18 +160,9 @@ class Fraction(numbers.Rational):
if denominator == 0:
raise ZeroDivisionError('Fraction(%s, 0)' % numerator)
- if _normalize:
- if type(numerator) is int is type(denominator):
- # *very* normal case
- g = math.gcd(numerator, denominator)
- if denominator < 0:
- g = -g
- else:
- g = _gcd(numerator, denominator)
- numerator //= g
- denominator //= g
- self._numerator = numerator
- self._denominator = denominator
+ g = gcd(numerator, denominator)
+ self._numerator = numerator // g
+ self._denominator = denominator // g
return self
@classmethod
@@ -202,6 +177,8 @@ class Fraction(numbers.Rational):
elif not isinstance(f, float):
raise TypeError("%s.from_float() only takes floats, not %r (%s)" %
(cls.__name__, f, type(f).__name__))
+ if math.isnan(f) or math.isinf(f):
+ raise TypeError("Cannot convert %r to %s." % (f, cls.__name__))
return cls(*f.as_integer_ratio())
@classmethod
@@ -214,15 +191,17 @@ class Fraction(numbers.Rational):
raise TypeError(
"%s.from_decimal() only takes Decimals, not %r (%s)" %
(cls.__name__, dec, type(dec).__name__))
- return cls(*dec.as_integer_ratio())
-
- def as_integer_ratio(self):
- """Return the integer ratio as a tuple.
-
- Return a tuple of two integers, whose ratio is equal to the
- Fraction and with a positive denominator.
- """
- return (self._numerator, self._denominator)
+ if not dec.is_finite():
+ # Catches infinities and nans.
+ raise TypeError("Cannot convert %s to %s." % (dec, cls.__name__))
+ sign, digits, exp = dec.as_tuple()
+ digits = int(''.join(map(str, digits)))
+ if sign:
+ digits = -digits
+ if exp >= 0:
+ return cls(digits * 10 ** exp)
+ else:
+ return cls(digits, 10 ** -exp)
def limit_denominator(self, max_denominator=1000000):
"""Closest Fraction to self with denominator at most max_denominator.
@@ -289,8 +268,7 @@ class Fraction(numbers.Rational):
def __repr__(self):
"""repr(self)"""
- return '%s(%s, %s)' % (self.__class__.__name__,
- self._numerator, self._denominator)
+ return ('Fraction(%s, %s)' % (self._numerator, self._denominator))
def __str__(self):
"""str(self)"""
@@ -315,7 +293,7 @@ class Fraction(numbers.Rational):
def __add__(self, other):
# Both types have numerators/denominator attributes,
# so do the operation directly
- if isinstance(other, (int, Fraction)):
+ if isinstance(other, (int, long, Fraction)):
return Fraction(self.numerator * other.denominator +
other.numerator * self.denominator,
self.denominator * other.denominator)
@@ -331,7 +309,7 @@ class Fraction(numbers.Rational):
def __radd__(self, other):
# radd handles more types than add because there's
# nothing left to fall back to.
- if isinstance(other, numbers.Rational):
+ if isinstance(other, Rational):
return Fraction(self.numerator * other.denominator +
other.numerator * self.denominator,
self.denominator * other.denominator)
@@ -380,7 +358,7 @@ class Fraction(numbers.Rational):
"""
def forward(a, b):
- if isinstance(b, (int, Fraction)):
+ if isinstance(b, (int, long, Fraction)):
return monomorphic_operator(a, b)
elif isinstance(b, float):
return fallback_operator(float(a), b)
@@ -392,7 +370,7 @@ class Fraction(numbers.Rational):
forward.__doc__ = monomorphic_operator.__doc__
def reverse(b, a):
- if isinstance(a, numbers.Rational):
+ if isinstance(a, Rational):
# Includes ints.
return monomorphic_operator(a, b)
elif isinstance(a, numbers.Real):
@@ -408,17 +386,17 @@ class Fraction(numbers.Rational):
def _add(a, b):
"""a + b"""
- da, db = a.denominator, b.denominator
- return Fraction(a.numerator * db + b.numerator * da,
- da * db)
+ 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"""
- da, db = a.denominator, b.denominator
- return Fraction(a.numerator * db - b.numerator * da,
- da * db)
+ return Fraction(a.numerator * b.denominator -
+ b.numerator * a.denominator,
+ a.denominator * b.denominator)
__sub__, __rsub__ = _operator_fallbacks(_sub, operator.sub)
@@ -434,27 +412,41 @@ class Fraction(numbers.Rational):
a.denominator * b.numerator)
__truediv__, __rtruediv__ = _operator_fallbacks(_div, operator.truediv)
+ __div__, __rdiv__ = _operator_fallbacks(_div, operator.div)
- def _floordiv(a, b):
+ def __floordiv__(a, b):
"""a // b"""
- return (a.numerator * b.denominator) // (a.denominator * b.numerator)
-
- __floordiv__, __rfloordiv__ = _operator_fallbacks(_floordiv, operator.floordiv)
-
- def _divmod(a, b):
- """(a // b, a % b)"""
- da, db = a.denominator, b.denominator
- div, n_mod = divmod(a.numerator * db, da * b.numerator)
- return div, Fraction(n_mod, da * db)
+ # 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)
- __divmod__, __rdivmod__ = _operator_fallbacks(_divmod, divmod)
+ 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):
+ def __mod__(a, b):
"""a % b"""
- da, db = a.denominator, b.denominator
- return Fraction((a.numerator * db) % (b.numerator * da), da * db)
+ div = a // b
+ return a - b * div
- __mod__, __rmod__ = _operator_fallbacks(_mod, operator.mod)
+ def __rmod__(b, a):
+ """a % b"""
+ div = a // b
+ return a - b * div
def __pow__(a, b):
"""a ** b
@@ -464,21 +456,15 @@ class Fraction(numbers.Rational):
result will be rational.
"""
- if isinstance(b, numbers.Rational):
+ if isinstance(b, Rational):
if b.denominator == 1:
power = b.numerator
if power >= 0:
return Fraction(a._numerator ** power,
- a._denominator ** power,
- _normalize=False)
- elif a._numerator >= 0:
- return Fraction(a._denominator ** -power,
- a._numerator ** -power,
- _normalize=False)
+ a._denominator ** power)
else:
- return Fraction((-a._denominator) ** -power,
- (-a._numerator) ** -power,
- _normalize=False)
+ return Fraction(a._denominator ** -power,
+ a._numerator ** -power)
else:
# A fractional power will generally produce an
# irrational number.
@@ -492,7 +478,7 @@ class Fraction(numbers.Rational):
# If a is an int, keep it that way if possible.
return a ** b._numerator
- if isinstance(a, numbers.Rational):
+ if isinstance(a, Rational):
return Fraction(a.numerator, a.denominator) ** b
if b._denominator == 1:
@@ -502,15 +488,15 @@ class Fraction(numbers.Rational):
def __pos__(a):
"""+a: Coerces a subclass instance to Fraction"""
- return Fraction(a._numerator, a._denominator, _normalize=False)
+ return Fraction(a._numerator, a._denominator)
def __neg__(a):
"""-a"""
- return Fraction(-a._numerator, a._denominator, _normalize=False)
+ return Fraction(-a._numerator, a._denominator)
def __abs__(a):
"""abs(a)"""
- return Fraction(abs(a._numerator), a._denominator, _normalize=False)
+ return Fraction(abs(a._numerator), a._denominator)
def __trunc__(a):
"""trunc(a)"""
@@ -519,78 +505,28 @@ class Fraction(numbers.Rational):
else:
return a._numerator // a._denominator
- def __floor__(a):
- """math.floor(a)"""
- return a.numerator // a.denominator
-
- def __ceil__(a):
- """math.ceil(a)"""
- # The negations cleverly convince floordiv to return the ceiling.
- return -(-a.numerator // a.denominator)
+ def __hash__(self):
+ """hash(self)
- def __round__(self, ndigits=None):
- """round(self, ndigits)
+ Tricky because values that are exactly representable as a
+ float must have the same hash as that float.
- Rounds half toward even.
"""
- if ndigits is None:
- floor, remainder = divmod(self.numerator, self.denominator)
- if remainder * 2 < self.denominator:
- return floor
- elif remainder * 2 > self.denominator:
- return floor + 1
- # Deal with the half case:
- elif floor % 2 == 0:
- return floor
- else:
- return floor + 1
- shift = 10**abs(ndigits)
- # See _operator_fallbacks.forward to check that the results of
- # these operations will always be Fraction and therefore have
- # round().
- if ndigits > 0:
- return Fraction(round(self * shift), shift)
- else:
- return Fraction(round(self / shift) * shift)
-
- def __hash__(self):
- """hash(self)"""
-
- # To make sure that the hash of a Fraction agrees with the hash
- # of a numerically equal integer, float or Decimal instance, we
- # follow the rules for numeric hashes outlined in the
- # documentation. (See library docs, 'Built-in Types').
-
- try:
- dinv = pow(self._denominator, -1, _PyHASH_MODULUS)
- except ValueError:
- # ValueError means there is no modular inverse.
- hash_ = _PyHASH_INF
+ # 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:
- # The general algorithm now specifies that the absolute value of
- # the hash is
- # (|N| * dinv) % P
- # where N is self._numerator and P is _PyHASH_MODULUS. That's
- # optimized here in two ways: first, for a non-negative int i,
- # hash(i) == i % P, but the int hash implementation doesn't need
- # to divide, and is faster than doing % P explicitly. So we do
- # hash(|N| * dinv)
- # instead. Second, N is unbounded, so its product with dinv may
- # be arbitrarily expensive to compute. The final answer is the
- # same if we use the bounded |N| % P instead, which can again
- # be done with an int hash() call. If 0 <= i < P, hash(i) == i,
- # so this nested hash() call wastes a bit of time making a
- # redundant copy when |N| < P, but can save an arbitrarily large
- # amount of computation for large |N|.
- hash_ = hash(hash(abs(self._numerator)) * dinv)
- result = hash_ if self._numerator >= 0 else -hash_
- return -2 if result == -1 else result
+ # 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 type(b) is int:
- return a._numerator == b and a._denominator == 1
- if isinstance(b, numbers.Rational):
+ if isinstance(b, Rational):
return (a._numerator == b.numerator and
a._denominator == b.denominator)
if isinstance(b, numbers.Complex) and b.imag == 0:
@@ -618,9 +554,13 @@ class Fraction(numbers.Rational):
"""
# convert other to a Rational instance where reasonable.
- if isinstance(other, numbers.Rational):
+ if isinstance(other, Rational):
return op(self._numerator * other.denominator,
self._denominator * other.numerator)
+ # comparisons with complex should raise a TypeError, for consistency
+ # with int<->complex, float<->complex, and complex<->complex comparisons.
+ if isinstance(other, complex):
+ raise TypeError("no ordering relation is defined for complex numbers")
if isinstance(other, float):
if math.isnan(other) or math.isinf(other):
return op(0.0, other)
@@ -645,7 +585,7 @@ class Fraction(numbers.Rational):
"""a >= b"""
return a._richcmp(b, operator.ge)
- def __bool__(a):
+ def __nonzero__(a):
"""a != 0"""
return a._numerator != 0