[Python-checkins] r60344 - in python/trunk/Lib: rational.py test/test_rational.py

jeffrey.yasskin python-checkins at python.org
Sun Jan 27 06:40:36 CET 2008


Author: jeffrey.yasskin
Date: Sun Jan 27 06:40:35 2008
New Revision: 60344

Modified:
   python/trunk/Lib/rational.py
   python/trunk/Lib/test/test_rational.py
Log:
Make rational.gcd() public and allow Rational to take decimal strings, per
Raymond's advice.


Modified: python/trunk/Lib/rational.py
==============================================================================
--- python/trunk/Lib/rational.py	(original)
+++ python/trunk/Lib/rational.py	Sun Jan 27 06:40:35 2008
@@ -14,8 +14,8 @@
 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 @@
     >>> _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 @@
 
 
 _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 @@
     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 @@
     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 @@
                 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 @@
         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

Modified: python/trunk/Lib/test/test_rational.py
==============================================================================
--- python/trunk/Lib/test/test_rational.py	(original)
+++ python/trunk/Lib/test/test_rational.py	Sun Jan 27 06:40:35 2008
@@ -9,10 +9,28 @@
 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 @@
         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 @@
             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 @@
         self.assertEqual(id(r), id(deepcopy(r)))
 
 def test_main():
-    run_unittest(RationalTest)
+    run_unittest(RationalTest, GcdTest)
 
 if __name__ == '__main__':
     test_main()


More information about the Python-checkins mailing list