diff options
author | Jeffrey Yasskin <jyasskin@gmail.com> | 2008-01-19 09:56:06 (GMT) |
---|---|---|
committer | Jeffrey Yasskin <jyasskin@gmail.com> | 2008-01-19 09:56:06 (GMT) |
commit | 45169fbc800d573ddd01d3f440f264caaa93c622 (patch) | |
tree | b565b74e43fd651e0ee163faf7c406690e5fbbf0 /Lib/rational.py | |
parent | bf4c7c8c0d851b239425b53b676c71a75c7d40c8 (diff) | |
download | cpython-45169fbc800d573ddd01d3f440f264caaa93c622.zip cpython-45169fbc800d573ddd01d3f440f264caaa93c622.tar.gz cpython-45169fbc800d573ddd01d3f440f264caaa93c622.tar.bz2 |
Several tweaks: add construction from strings and .from_decimal(), change
__init__ to __new__ to enforce immutability, and remove "rational." from repr
and the parens from str.
Diffstat (limited to 'Lib/rational.py')
-rwxr-xr-x | Lib/rational.py | 76 |
1 files changed, 64 insertions, 12 deletions
diff --git a/Lib/rational.py b/Lib/rational.py index d455dc6..19e7f14 100755 --- a/Lib/rational.py +++ b/Lib/rational.py @@ -7,6 +7,7 @@ from __future__ import division import math import numbers import operator +import re __all__ = ["Rational"] @@ -76,6 +77,10 @@ def _binary_float_to_ratio(x): return (top, 2 ** -e) +_RATIONAL_FORMAT = re.compile( + r'^\s*(?P<sign>[-+]?)(?P<num>\d+)(?:/(?P<denom>\d+))?\s*$') + + class Rational(RationalAbc): """This class implements rational numbers. @@ -84,18 +89,41 @@ class Rational(RationalAbc): 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') - def __init__(self, numerator=0, denominator=1): - if (not isinstance(numerator, numbers.Integral) and - isinstance(numerator, RationalAbc) and - denominator == 1): - # Handle copies from other rationals. - other_rational = numerator - numerator = other_rational.numerator - denominator = other_rational.denominator + # We're immutable, so use __new__ not __init__ + def __new__(cls, numerator=0, denominator=1): + """Constructs a Rational. + + Takes a string, 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 = int(m.group('num')) + # Default denominator to 1. That's the only optional group. + 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)): @@ -108,10 +136,15 @@ class Rational(RationalAbc): g = _gcd(numerator, denominator) self._numerator = int(numerator // g) self._denominator = int(denominator // g) + return self @classmethod def from_float(cls, f): - """Converts a float to a rational number, exactly.""" + """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("%s.from_float() only takes floats, not %r (%s)" % (cls.__name__, f, type(f).__name__)) @@ -119,6 +152,26 @@ class Rational(RationalAbc): raise TypeError("Cannot convert %r to %s." % (f, cls.__name__)) return cls(*_binary_float_to_ratio(f)) + @classmethod + def from_decimal(cls, dec): + """Converts a finite Decimal instance to a rational number, exactly.""" + from decimal import Decimal + if not isinstance(dec, Decimal): + raise TypeError( + "%s.from_decimal() only takes Decimals, not %r (%s)" % + (cls.__name__, dec, type(dec).__name__)) + 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) + @property def numerator(a): return a._numerator @@ -129,15 +182,14 @@ class Rational(RationalAbc): def __repr__(self): """repr(self)""" - return ('rational.Rational(%r,%r)' % - (self.numerator, self.denominator)) + 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) + return '%s/%s' % (self.numerator, self.denominator) def _operator_fallbacks(monomorphic_operator, fallback_operator): """Generates forward and reverse operators given a purely-rational |