aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/Lib/fractions.py
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/fractions.py')
-rw-r--r--Lib/fractions.py129
1 files changed, 75 insertions, 54 deletions
diff --git a/Lib/fractions.py b/Lib/fractions.py
index a0d86a43936..8be52d2db88 100644
--- a/Lib/fractions.py
+++ b/Lib/fractions.py
@@ -1,18 +1,17 @@
# Originally contributed by Sjoerd Mullender.
# Significantly modified by Jeffrey Yasskin <jyasskin at gmail.com>.
-"""Rational, infinite-precision, real numbers."""
+"""Fraction, 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):
@@ -25,6 +24,12 @@ def gcd(a, 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
@@ -41,7 +46,7 @@ _RATIONAL_FORMAT = re.compile(r"""
""", re.VERBOSE | re.IGNORECASE)
-class Fraction(Rational):
+class Fraction(numbers.Rational):
"""This class implements rational numbers.
In the two-argument form of the constructor, Fraction(8, 6) will
@@ -66,7 +71,7 @@ class Fraction(Rational):
# We're immutable, so use __new__ not __init__
def __new__(cls, numerator=0, denominator=None):
- """Constructs a Fraction.
+ """Constructs a Rational.
Takes a string like '3/2' or '1.5', another Rational instance, a
numerator/denominator pair, or a float.
@@ -99,7 +104,7 @@ class Fraction(Rational):
self = super(Fraction, cls).__new__(cls)
if denominator is None:
- if isinstance(numerator, Rational):
+ if isinstance(numerator, numbers.Rational):
self._numerator = numerator.numerator
self._denominator = numerator.denominator
return self
@@ -117,7 +122,7 @@ class Fraction(Rational):
self._denominator = value._denominator
return self
- elif isinstance(numerator, basestring):
+ elif isinstance(numerator, str):
# Handle construction from strings.
m = _RATIONAL_FORMAT.match(numerator)
if m is None:
@@ -148,8 +153,8 @@ class Fraction(Rational):
raise TypeError("argument should be a string "
"or a Rational instance")
- elif (isinstance(numerator, Rational) and
- isinstance(denominator, Rational)):
+ elif (isinstance(numerator, numbers.Rational) and
+ isinstance(denominator, numbers.Rational)):
numerator, denominator = (
numerator.numerator * denominator.denominator,
denominator.numerator * numerator.denominator
@@ -293,7 +298,7 @@ class Fraction(Rational):
def __add__(self, other):
# Both types have numerators/denominator attributes,
# so do the operation directly
- if isinstance(other, (int, long, Fraction)):
+ if isinstance(other, (int, Fraction)):
return Fraction(self.numerator * other.denominator +
other.numerator * self.denominator,
self.denominator * other.denominator)
@@ -309,7 +314,7 @@ class Fraction(Rational):
def __radd__(self, other):
# radd handles more types than add because there's
# nothing left to fall back to.
- if isinstance(other, Rational):
+ if isinstance(other, numbers.Rational):
return Fraction(self.numerator * other.denominator +
other.numerator * self.denominator,
self.denominator * other.denominator)
@@ -358,7 +363,7 @@ class Fraction(Rational):
"""
def forward(a, b):
- if isinstance(b, (int, long, Fraction)):
+ if isinstance(b, (int, Fraction)):
return monomorphic_operator(a, b)
elif isinstance(b, float):
return fallback_operator(float(a), b)
@@ -370,7 +375,7 @@ class Fraction(Rational):
forward.__doc__ = monomorphic_operator.__doc__
def reverse(b, a):
- if isinstance(a, Rational):
+ if isinstance(a, numbers.Rational):
# Includes ints.
return monomorphic_operator(a, b)
elif isinstance(a, numbers.Real):
@@ -412,31 +417,14 @@ class Fraction(Rational):
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)
+ return math.floor(a / b)
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)
+ return math.floor(a / b)
def __mod__(a, b):
"""a % b"""
@@ -456,7 +444,7 @@ class Fraction(Rational):
result will be rational.
"""
- if isinstance(b, Rational):
+ if isinstance(b, numbers.Rational):
if b.denominator == 1:
power = b.numerator
if power >= 0:
@@ -478,7 +466,7 @@ class Fraction(Rational):
# If a is an int, keep it that way if possible.
return a ** b._numerator
- if isinstance(a, Rational):
+ if isinstance(a, numbers.Rational):
return Fraction(a.numerator, a.denominator) ** b
if b._denominator == 1:
@@ -505,28 +493,65 @@ class Fraction(Rational):
else:
return a._numerator // a._denominator
- def __hash__(self):
- """hash(self)
+ def __floor__(a):
+ """Will be math.floor(a) in 3.0."""
+ return a.numerator // a.denominator
- Tricky because values that are exactly representable as a
- float must have the same hash as that float.
+ def __ceil__(a):
+ """Will be math.ceil(a) in 3.0."""
+ # The negations cleverly convince floordiv to return the ceiling.
+ return -(-a.numerator // a.denominator)
+ def __round__(self, ndigits=None):
+ """Will be round(self, ndigits) in 3.0.
+
+ 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)"""
+
# 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))
+
+ # In order 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').
+
+ # dinv is the inverse of self._denominator modulo the prime
+ # _PyHASH_MODULUS, or 0 if self._denominator is divisible by
+ # _PyHASH_MODULUS.
+ dinv = pow(self._denominator, _PyHASH_MODULUS - 2, _PyHASH_MODULUS)
+ if not dinv:
+ hash_ = _PyHASH_INF
else:
- # Use tuple's hash to avoid a high collision rate on
- # simple fractions.
- return hash((self._numerator, self._denominator))
+ hash_ = abs(self._numerator) * dinv % _PyHASH_MODULUS
+ result = hash_ if self >= 0 else -hash_
+ return -2 if result == -1 else result
def __eq__(a, b):
"""a == b"""
- if isinstance(b, Rational):
+ if isinstance(b, numbers.Rational):
return (a._numerator == b.numerator and
a._denominator == b.denominator)
if isinstance(b, numbers.Complex) and b.imag == 0:
@@ -554,13 +579,9 @@ class Fraction(Rational):
"""
# convert other to a Rational instance where reasonable.
- if isinstance(other, Rational):
+ if isinstance(other, numbers.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)
@@ -585,7 +606,7 @@ class Fraction(Rational):
"""a >= b"""
return a._richcmp(b, operator.ge)
- def __nonzero__(a):
+ def __bool__(a):
"""a != 0"""
return a._numerator != 0