[Python-checkins] r79670 - in python/branches/py3k: Doc/library/fractions.rst Lib/fractions.py Lib/test/test_fractions.py Misc/NEWS

mark.dickinson python-checkins at python.org
Sat Apr 3 13:18:53 CEST 2010


Author: mark.dickinson
Date: Sat Apr  3 13:18:52 2010
New Revision: 79670

Log:
Merged revisions 79629 via svnmerge from 
svn+ssh://pythondev@svn.python.org/python/trunk

........
  r79629 | mark.dickinson | 2010-04-02 23:27:36 +0100 (Fri, 02 Apr 2010) | 2 lines
  
  Issue #8294:  Allow float and Decimal arguments in Fraction constructor.
........


Modified:
   python/branches/py3k/   (props changed)
   python/branches/py3k/Doc/library/fractions.rst
   python/branches/py3k/Lib/fractions.py
   python/branches/py3k/Lib/test/test_fractions.py
   python/branches/py3k/Misc/NEWS

Modified: python/branches/py3k/Doc/library/fractions.rst
==============================================================================
--- python/branches/py3k/Doc/library/fractions.rst	(original)
+++ python/branches/py3k/Doc/library/fractions.rst	Sat Apr  3 13:18:52 2010
@@ -15,17 +15,24 @@
 
 .. class:: Fraction(numerator=0, denominator=1)
            Fraction(other_fraction)
+           Fraction(float)
+           Fraction(decimal)
            Fraction(string)
 
-   The first version requires that *numerator* and *denominator* are
-   instances of :class:`numbers.Rational` and returns a new
-   :class:`Fraction` instance with value ``numerator/denominator``. If
-   *denominator* is :const:`0`, it raises a
-   :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.  The
-   usual form for this string is::
+   The first version requires that *numerator* and *denominator* are instances
+   of :class:`numbers.Rational` and returns a new :class:`Fraction` instance
+   with value ``numerator/denominator``. If *denominator* is :const:`0`, it
+   raises a :exc:`ZeroDivisionError`. The second version requires that
+   *other_fraction* is an instance of :class:`numbers.Rational` and returns a
+   :class:`Fraction` instance with the same value.  The next two versions accept
+   either a :class:`float` or a :class:`decimal.Decimal` instance, and return a
+   :class:`Fraction` instance with exactly the same value.  Note that due to the
+   usual issues with binary floating-point (see :ref:`tut-fp-issues`), the
+   argument to ``Fraction(1.1)`` is not exactly equal to 11/10, and so
+   ``Fraction(1.1)`` does *not* return ``Fraction(11, 10)`` as one might expect.
+   (But see the documentation for the :meth:`limit_denominator` method below.)
+   The last version of the constructor expects a string or unicode instance.
+   The usual form for this instance is::
 
       [sign] numerator ['/' denominator]
 
@@ -55,6 +62,13 @@
       Fraction(-1, 8)
       >>> Fraction('7e-6')
       Fraction(7, 1000000)
+      >>> Fraction(2.25)
+      Fraction(9, 4)
+      >>> Fraction(1.1)
+      Fraction(2476979795053773, 2251799813685248)
+      >>> from decimal import Decimal
+      >>> Fraction(Decimal('1.1'))
+      Fraction(11, 10)
 
 
    The :class:`Fraction` class inherits from the abstract base class
@@ -63,6 +77,10 @@
    and should be treated as immutable.  In addition,
    :class:`Fraction` has the following methods:
 
+   .. versionchanged:: 3.2
+      The :class:`Fraction` constructor now accepts :class:`float` and
+      :class:`decimal.Decimal` instances.
+
 
    .. method:: from_float(flt)
 
@@ -70,12 +88,19 @@
       value of *flt*, which must be a :class:`float`. Beware that
       ``Fraction.from_float(0.3)`` is not the same value as ``Fraction(3, 10)``
 
+      .. note:: From Python 3.2 onwards, you can also construct a
+         :class:`Fraction` instance directly from a :class:`float`.
+
 
    .. method:: from_decimal(dec)
 
       This class method constructs a :class:`Fraction` representing the exact
       value of *dec*, which must be a :class:`decimal.Decimal` instance.
 
+      .. note:: From Python 3.2 onwards, you can also construct a
+         :class:`Fraction` instance directly from a :class:`decimal.Decimal`
+         instance.
+
 
    .. method:: limit_denominator(max_denominator=1000000)
 
@@ -90,10 +115,12 @@
       or for recovering a rational number that's represented as a float:
 
          >>> from math import pi, cos
-         >>> Fraction.from_float(cos(pi/3))
+         >>> Fraction(cos(pi/3))
          Fraction(4503599627370497, 9007199254740992)
-         >>> Fraction.from_float(cos(pi/3)).limit_denominator()
+         >>> Fraction(cos(pi/3)).limit_denominator()
          Fraction(1, 2)
+         >>> Fraction(1.1).limit_denominator()
+         Fraction(11, 10)
 
 
    .. method:: __floor__()

Modified: python/branches/py3k/Lib/fractions.py
==============================================================================
--- python/branches/py3k/Lib/fractions.py	(original)
+++ python/branches/py3k/Lib/fractions.py	Sat Apr  3 13:18:52 2010
@@ -3,6 +3,7 @@
 
 """Fraction, infinite-precision, real numbers."""
 
+from decimal import Decimal
 import math
 import numbers
 import operator
@@ -41,13 +42,21 @@
 class Fraction(numbers.Rational):
     """This class implements rational numbers.
 
-    Fraction(8, 6) will produce a rational number equivalent to
-    4/3. Both arguments must be Integral. The numerator defaults to 0
-    and the denominator defaults to 1 so that Fraction(3) == 3 and
-    Fraction() == 0.
+    In the two-argument form of the constructor, Fraction(8, 6) will
+    produce a rational number equivalent to 4/3. Both arguments must
+    be Rational. The numerator defaults to 0 and the denominator
+    defaults to 1 so that Fraction(3) == 3 and Fraction() == 0.
 
-    Fraction can also be constructed from strings of the form
-    '[-+]?[0-9]+((/|.)[0-9]+)?', optionally surrounded by spaces.
+    Fractions can also be constructed from:
+
+      - numeric strings similar to those accepted by the
+        float constructor (for example, '-2.3' or '1e10')
+
+      - strings of the form '123/456'
+
+      - float and Decimal instances
+
+      - other Rational instances (including integers)
 
     """
 
@@ -57,8 +66,32 @@
     def __new__(cls, numerator=0, denominator=None):
         """Constructs a Rational.
 
-        Takes a string like '3/2' or '1.5', another Rational, or a
-        numerator/denominator pair.
+        Takes a string like '3/2' or '1.5', another Rational instance, a
+        numerator/denominator pair, or a float.
+
+        Examples
+        --------
+
+        >>> Fraction(10, -8)
+        Fraction(-5, 4)
+        >>> Fraction(Fraction(1, 7), 5)
+        Fraction(1, 35)
+        >>> Fraction(Fraction(1, 7), Fraction(2, 3))
+        Fraction(3, 14)
+        >>> Fraction('314')
+        Fraction(314, 1)
+        >>> Fraction('-35/4')
+        Fraction(-35, 4)
+        >>> Fraction('3.1415') # conversion from numeric string
+        Fraction(6283, 2000)
+        >>> Fraction('-47e-2') # string may include a decimal exponent
+        Fraction(-47, 100)
+        >>> Fraction(1.47)  # direct construction from float (exact conversion)
+        Fraction(6620291452234629, 4503599627370496)
+        >>> Fraction(2.25)
+        Fraction(9, 4)
+        >>> Fraction(Decimal('1.47'))
+        Fraction(147, 100)
 
         """
         self = super(Fraction, cls).__new__(cls)
@@ -69,6 +102,19 @@
                 self._denominator = numerator.denominator
                 return self
 
+            elif isinstance(numerator, float):
+                # Exact conversion from float
+                value = Fraction.from_float(numerator)
+                self._numerator = value._numerator
+                self._denominator = value._denominator
+                return self
+
+            elif isinstance(numerator, Decimal):
+                value = Fraction.from_decimal(numerator)
+                self._numerator = value._numerator
+                self._denominator = value._denominator
+                return self
+
             elif isinstance(numerator, str):
                 # Handle construction from strings.
                 m = _RATIONAL_FORMAT.match(numerator)

Modified: python/branches/py3k/Lib/test/test_fractions.py
==============================================================================
--- python/branches/py3k/Lib/test/test_fractions.py	(original)
+++ python/branches/py3k/Lib/test/test_fractions.py	Sat Apr  3 13:18:52 2010
@@ -12,6 +12,11 @@
 F = fractions.Fraction
 gcd = fractions.gcd
 
+# decorator for skipping tests on non-IEEE 754 platforms
+requires_IEEE_754 = unittest.skipUnless(
+    float.__getformat__("double").startswith("IEEE"),
+    "test requires IEEE 754 doubles")
+
 class DummyFloat(object):
     """Dummy float class for testing comparisons with Fractions"""
 
@@ -130,13 +135,33 @@
 
         self.assertRaisesMessage(ZeroDivisionError, "Fraction(12, 0)",
                                  F, 12, 0)
-        self.assertRaises(TypeError, F, 1.5)
         self.assertRaises(TypeError, F, 1.5 + 3j)
 
         self.assertRaises(TypeError, F, "3/2", 3)
         self.assertRaises(TypeError, F, 3, 0j)
         self.assertRaises(TypeError, F, 3, 1j)
 
+    @requires_IEEE_754
+    def testInitFromFloat(self):
+        self.assertEquals((5, 2), _components(F(2.5)))
+        self.assertEquals((0, 1), _components(F(-0.0)))
+        self.assertEquals((3602879701896397, 36028797018963968),
+                          _components(F(0.1)))
+        self.assertRaises(TypeError, F, float('nan'))
+        self.assertRaises(TypeError, F, float('inf'))
+        self.assertRaises(TypeError, F, float('-inf'))
+
+    def testInitFromDecimal(self):
+        self.assertEquals((11, 10),
+                          _components(F(Decimal('1.1'))))
+        self.assertEquals((7, 200),
+                          _components(F(Decimal('3.5e-2'))))
+        self.assertEquals((0, 1),
+                          _components(F(Decimal('.000e20'))))
+        self.assertRaises(TypeError, F, Decimal('nan'))
+        self.assertRaises(TypeError, F, Decimal('snan'))
+        self.assertRaises(TypeError, F, Decimal('inf'))
+        self.assertRaises(TypeError, F, Decimal('-inf'))
 
     def testFromString(self):
         self.assertEquals((5, 1), _components(F("5")))

Modified: python/branches/py3k/Misc/NEWS
==============================================================================
--- python/branches/py3k/Misc/NEWS	(original)
+++ python/branches/py3k/Misc/NEWS	Sat Apr  3 13:18:52 2010
@@ -301,6 +301,9 @@
 Library
 -------
 
+- Issue #8294: The Fraction constructor now accepts Decimal and float
+  instances directly.
+
 - Issue #7279: Comparisons involving a Decimal signaling NaN now
   signal InvalidOperation instead of returning False.  (Comparisons
   involving a quiet NaN are unchanged.)  Also, Decimal quiet NaNs


More information about the Python-checkins mailing list