diff options
author | Jeffrey Yasskin <jyasskin@gmail.com> | 2008-01-27 05:40:35 (GMT) |
---|---|---|
committer | Jeffrey Yasskin <jyasskin@gmail.com> | 2008-01-27 05:40:35 (GMT) |
commit | 3e1a3736169fbd7e1a2234899faa64e5de75bc3e (patch) | |
tree | 9270a62b634fb382825130180054c50d72446eed | |
parent | 46c61b2c1dc082da1ab016e74c7074c3b653a358 (diff) | |
download | cpython-3e1a3736169fbd7e1a2234899faa64e5de75bc3e.zip cpython-3e1a3736169fbd7e1a2234899faa64e5de75bc3e.tar.gz cpython-3e1a3736169fbd7e1a2234899faa64e5de75bc3e.tar.bz2 |
Make rational.gcd() public and allow Rational to take decimal strings, per
Raymond's advice.
-rwxr-xr-x | Lib/rational.py | 37 | ||||
-rw-r--r-- | Lib/test/test_rational.py | 46 |
2 files changed, 62 insertions, 21 deletions
diff --git a/Lib/rational.py b/Lib/rational.py index e2083db..93b0c5b 100755 --- a/Lib/rational.py +++ b/Lib/rational.py @@ -14,8 +14,8 @@ __all__ = ["Rational"] RationalAbc = numbers.Rational -def _gcd(a, b): # XXX This is a useful function. Consider making it public. - """Calculate the Greatest Common Divisor. +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). @@ -40,8 +40,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() + # XXX Move this to floatobject.c with a name like + # float.as_integer_ratio() if x == 0: return 0, 1 @@ -80,12 +80,9 @@ def _binary_float_to_ratio(x): _RATIONAL_FORMAT = re.compile( - r'^\s*(?P<sign>[-+]?)(?P<num>\d+)(?:/(?P<denom>\d+))?\s*$') + r'^\s*(?P<sign>[-+]?)(?P<num>\d+)' + r'(?:/(?P<denom>\d+)|\.(?P<decimal>\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. @@ -96,7 +93,7 @@ class Rational(RationalAbc): Rational() == 0. Rationals can also be constructed from strings of the form - '[-+]?[0-9]+(/[0-9]+)?', optionally surrounded by spaces. + '[-+]?[0-9]+((/|.)[0-9]+)?', optionally surrounded by spaces. """ @@ -106,7 +103,8 @@ class Rational(RationalAbc): def __new__(cls, numerator=0, denominator=1): """Constructs a Rational. - Takes a string, another Rational, or a numerator/denominator pair. + Takes a string like '3/2' or '3.2', another Rational, or a + numerator/denominator pair. """ self = super(Rational, cls).__new__(cls) @@ -118,9 +116,18 @@ class Rational(RationalAbc): 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) + 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 @@ -139,7 +146,7 @@ class Rational(RationalAbc): if denominator == 0: raise ZeroDivisionError('Rational(%s, 0)' % numerator) - g = _gcd(numerator, denominator) + g = gcd(numerator, denominator) self.numerator = int(numerator // g) self.denominator = int(denominator // g) return self diff --git a/Lib/test/test_rational.py b/Lib/test/test_rational.py index 6ce7c35..bd700b1 100644 --- a/Lib/test/test_rational.py +++ b/Lib/test/test_rational.py @@ -9,10 +9,28 @@ 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): @@ -55,8 +73,12 @@ class RationalTest(unittest.TestCase): 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((3, 2), _components(R(" 03/02 \n "))) - self.assertEquals((3, 2), _components(R(u" 03/02 \n "))) + 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.assertRaisesMessage( ZeroDivisionError, "Rational(3, 0)", @@ -76,9 +98,21 @@ class RationalTest(unittest.TestCase): ValueError, "Invalid literal for Rational: + 3/2", R, "+ 3/2") self.assertRaisesMessage( - # Only parse fractions, not decimals. - ValueError, "Invalid literal for Rational: 3.2", - R, "3.2") + # 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") def testImmutable(self): r = R(7, 3) @@ -368,7 +402,7 @@ class RationalTest(unittest.TestCase): self.assertEqual(id(r), id(deepcopy(r))) def test_main(): - run_unittest(RationalTest) + run_unittest(RationalTest, GcdTest) if __name__ == '__main__': test_main() |