]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
Issue #5812: Make Fraction('1e6') valid. The Fraction constructor now
authorMark Dickinson <dickinsm@gmail.com>
Wed, 22 Apr 2009 17:50:21 +0000 (17:50 +0000)
committerMark Dickinson <dickinsm@gmail.com>
Wed, 22 Apr 2009 17:50:21 +0000 (17:50 +0000)
accepts all strings accepted by the float and Decimal constructors,
with the exception of strings representing NaNs or infinities.

Doc/library/fractions.rst
Lib/fractions.py
Lib/test/test_fractions.py
Misc/NEWS

index 68a8ef62957e4ed6cecb0661053acbc313c9ddfd..c135f9197a3fd3dd97836bec559965960e16e9de 100644 (file)
@@ -25,21 +25,18 @@ another rational number, or from a string.
    :exc:`ZeroDivisionError`. The second version requires that
    *other_fraction* is an instance of :class:`numbers.Rational` and
    returns an :class:`Fraction` instance with the same value.  The
-   last version of the constructor expects a string
-   instance in one of two possible forms.  The first form is::
+   last version of the constructor expects a string instance.  The
+   usual form for this string is::
 
       [sign] numerator ['/' denominator]
 
    where the optional ``sign`` may be either '+' or '-' and
    ``numerator`` and ``denominator`` (if present) are strings of
-   decimal digits.  The second permitted form is that of a number
-   containing a decimal point::
-
-      [sign] integer '.' [fraction] | [sign] '.' fraction
-
-   where ``integer`` and ``fraction`` are strings of digits.  In
-   either form the input string may also have leading and/or trailing
-   whitespace.  Here are some examples::
+   decimal digits.  In addition, any string that represents a finite
+   value and is accepted by the :class:`float` constructor is also
+   accepted by the :class:`Fraction` constructor.  In either form the
+   input string may also have leading and/or trailing whitespace.
+   Here are some examples::
 
       >>> from fractions import Fraction
       >>> Fraction(16, -10)
@@ -57,6 +54,8 @@ another rational number, or from a string.
       Fraction(1414213, 1000000)
       >>> Fraction('-.125')
       Fraction(-1, 8)
+      >>> Fraction('7e-6')
+      Fraction(7, 1000000)
 
 
    The :class:`Fraction` class inherits from the abstract base class
index ed1e9a0c1035e570366840efbf0405172a724117..5242f8f88ca7fd3d255aa5e324669db9e0704658 100755 (executable)
@@ -28,13 +28,14 @@ _RATIONAL_FORMAT = re.compile(r"""
     (?P<sign>[-+]?)            # an optional sign, then
     (?=\d|\.\d)                # lookahead for digit or .digit
     (?P<num>\d*)               # numerator (possibly empty)
-    (?:                        # followed by an optional
-       /(?P<denom>\d+)         # / and denominator
+    (?:                        # followed by
+       (?:/(?P<denom>\d+))?    # an optional denominator
     |                          # or
-       \.(?P<decimal>\d*)      # decimal point and fractional part
-    )?
+       (?:\.(?P<decimal>\d*))? # an optional fractional part
+       (?:E(?P<exp>[-+]?\d+))? # and optional exponent
+    )
     \s*\Z                      # and optional whitespace to finish
-""", re.VERBOSE)
+""", re.VERBOSE | re.IGNORECASE)
 
 
 class Fraction(numbers.Rational):
@@ -65,22 +66,28 @@ class Fraction(numbers.Rational):
         if not isinstance(numerator, int) and denominator == 1:
             if isinstance(numerator, str):
                 # Handle construction from strings.
-                input = numerator
-                m = _RATIONAL_FORMAT.match(input)
+                m = _RATIONAL_FORMAT.match(numerator)
                 if m is None:
-                    raise ValueError('Invalid literal for Fraction: %r' % input)
-                numerator = m.group('num')
-                decimal = m.group('decimal')
-                if decimal:
-                    # The literal is a decimal number.
-                    numerator = int(numerator + decimal)
-                    denominator = 10**len(decimal)
+                    raise ValueError('Invalid literal for Fraction: %r' %
+                                     numerator)
+                numerator = int(m.group('num') or '0')
+                denom = m.group('denom')
+                if denom:
+                    denominator = int(denom)
                 else:
-                    # The literal is an integer or fraction.
-                    numerator = int(numerator)
-                    # Default denominator to 1.
-                    denominator = int(m.group('denom') or 1)
-
+                    denominator = 1
+                    decimal = m.group('decimal')
+                    if decimal:
+                        scale = 10**len(decimal)
+                        numerator = numerator * scale + int(decimal)
+                        denominator *= scale
+                    exp = m.group('exp')
+                    if exp:
+                        exp = int(exp)
+                        if exp >= 0:
+                            numerator *= 10**exp
+                        else:
+                            denominator *= 10**-exp
                 if m.group('sign') == '-':
                     numerator = -numerator
 
index 91fcd26d5c9cc42f598ba49661d79b66a5e31a5c..448e32da536bb662008fa75f0c3804754d992c5f 100644 (file)
@@ -78,6 +78,11 @@ class FractionTest(unittest.TestCase):
         self.assertEquals((-16, 5), _components(F(" -3.2 ")))
         self.assertEquals((-3, 1), _components(F(" -3. ")))
         self.assertEquals((3, 5), _components(F(" .6 ")))
+        self.assertEquals((1, 3125), _components(F("32.e-5")))
+        self.assertEquals((1000000, 1), _components(F("1E+06")))
+        self.assertEquals((-12300, 1), _components(F("-1.23e4")))
+        self.assertEquals((0, 1), _components(F(" .0e+0\t")))
+        self.assertEquals((0, 1), _components(F("-0.000e0")))
 
         self.assertRaisesMessage(
             ZeroDivisionError, "Fraction(3, 0)",
@@ -85,6 +90,9 @@ class FractionTest(unittest.TestCase):
         self.assertRaisesMessage(
             ValueError, "Invalid literal for Fraction: '3/'",
             F, "3/")
+        self.assertRaisesMessage(
+            ValueError, "Invalid literal for Fraction: '/2'",
+            F, "/2")
         self.assertRaisesMessage(
             ValueError, "Invalid literal for Fraction: '3 /2'",
             F, "3 /2")
@@ -100,10 +108,6 @@ class FractionTest(unittest.TestCase):
             # Avoid treating '.' as a regex special character.
             ValueError, "Invalid literal for Fraction: '3a2'",
             F, "3a2")
-        self.assertRaisesMessage(
-            # Only parse ordinary decimals, not scientific form.
-            ValueError, "Invalid literal for Fraction: '3.2e4'",
-            F, "3.2e4")
         self.assertRaisesMessage(
             # Don't accept combinations of decimals and rationals.
             ValueError, "Invalid literal for Fraction: '3/7.2'",
index cc70d78a9a511786717b01148c171a5e51c57480..c69672f0fc9b336de6c68819a9682ee5e9450a00 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -75,6 +75,10 @@ Core and Builtins
 Library
 -------
 
+- Issue #5812: Fraction('1e6') is valid: more generally, any string
+  that's valid for float() is now valid for Fraction(), with the
+  exception of strings representing NaNs and infinities.
+
 - Issue #5734: BufferedRWPair was poorly tested and had several glaring
   bugs. Patch by Brian Quinlan.