summaryrefslogtreecommitdiffstats
path: root/Lib/rational.py
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/rational.py')
-rwxr-xr-xLib/rational.py72
1 files changed, 48 insertions, 24 deletions
diff --git a/Lib/rational.py b/Lib/rational.py
index 4a56cf2..89b622c 100755
--- a/Lib/rational.py
+++ b/Lib/rational.py
@@ -13,7 +13,7 @@ __all__ = ["Rational"]
RationalAbc = numbers.Rational
-def _gcd(a, b):
+def _gcd(a, b): # XXX This is a useful function. Consider making it public.
"""Calculate the Greatest Common Divisor.
Unless b==0, the result will have the same sign as b (so that when
@@ -39,6 +39,8 @@ def _binary_float_to_ratio(x):
>>> _binary_float_to_ratio(-.25)
(-1, 4)
"""
+ # XXX Consider moving this to to floatobject.c
+ # with a name like float.as_intger_ratio()
if x == 0:
return 0, 1
@@ -79,6 +81,10 @@ def _binary_float_to_ratio(x):
_RATIONAL_FORMAT = re.compile(
r'^\s*(?P<sign>[-+]?)(?P<num>\d+)(?:/(?P<denom>\d+))?\s*$')
+# XXX Consider accepting decimal strings as input since they are exact.
+# Rational("2.01") --> s="2.01" ; Rational.from_decimal(Decimal(s)) --> Rational(201, 100)"
+# If you want to avoid going through the decimal module, just parse the string directly:
+# s.partition('.') --> ('2', '.', '01') --> Rational(int('2'+'01'), 10**len('01')) --> Rational(201, 100)
class Rational(RationalAbc):
"""This class implements rational numbers.
@@ -93,7 +99,7 @@ class Rational(RationalAbc):
"""
- __slots__ = ('_numerator', '_denominator')
+ __slots__ = ('numerator', 'denominator')
# We're immutable, so use __new__ not __init__
def __new__(cls, numerator=0, denominator=1):
@@ -133,8 +139,8 @@ class Rational(RationalAbc):
raise ZeroDivisionError('Rational(%s, 0)' % numerator)
g = _gcd(numerator, denominator)
- self._numerator = int(numerator // g)
- self._denominator = int(denominator // g)
+ self.numerator = int(numerator // g)
+ self.denominator = int(denominator // g)
return self
@classmethod
@@ -192,29 +198,22 @@ class Rational(RationalAbc):
n, d = d, n
return cf
- @classmethod
- def approximate_from_float(cls, f, max_denominator):
- 'Best rational approximation to f with a denominator <= max_denominator'
+ 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
- cf = cls.from_float(f).as_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 = cls.from_continued_fraction(cf[:i])
+ 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))
@@ -226,6 +225,16 @@ class Rational(RationalAbc):
else:
return '%s/%s' % (self.numerator, self.denominator)
+ """ XXX This section needs a lot more commentary
+
+ * Explain the typical sequence of checks, calls, and fallbacks.
+ * Explain the subtle reasons why this logic was needed.
+ * It is not clear how common cases are handled (for example, how
+ does the ratio of two huge integers get converted to a float
+ without overflowing the long-->float conversion.
+
+ """
+
def _operator_fallbacks(monomorphic_operator, fallback_operator):
"""Generates forward and reverse operators given a purely-rational
operator and a function from the operator module.
@@ -299,18 +308,15 @@ class Rational(RationalAbc):
"""a // b"""
return math.floor(a / b)
- @classmethod
- def _mod(cls, a, b):
- div = a // b
- return a - b * div
-
def __mod__(a, b):
"""a % b"""
- return a._mod(a, b)
+ div = a // b
+ return a - b * div
def __rmod__(b, a):
"""a % b"""
- return b._mod(a, b)
+ div = a // b
+ return a - b * div
def __pow__(a, b):
"""a ** b
@@ -369,6 +375,8 @@ class Rational(RationalAbc):
else:
return a.numerator // a.denominator
+ __int__ = __trunc__
+
def __floor__(a):
"""Will be math.floor(a) in 3.0."""
return a.numerator // a.denominator
@@ -410,6 +418,7 @@ class Rational(RationalAbc):
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)
@@ -481,3 +490,18 @@ class Rational(RationalAbc):
def __bool__(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)