From 0d250bc119489fa7d094d4a3fd2fd2fa0a508145 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 29 Dec 2015 22:34:23 +0200 Subject: [PATCH] Issue #25971: Optimized creating Fractions from floats by 2 times and from Decimals by 3 times. Unified error messages in float.as_integer_ratio(), Decimal.as_integer_ratio(), and Fraction constructors. --- Lib/_pydecimal.py | 6 ++---- Lib/fractions.py | 32 ++++---------------------------- Lib/test/test_fractions.py | 14 +++++++------- Misc/NEWS | 3 +++ Objects/floatobject.c | 12 ++++++------ 5 files changed, 22 insertions(+), 45 deletions(-) diff --git a/Lib/_pydecimal.py b/Lib/_pydecimal.py index eb7bba8e93a..02365cad2c3 100644 --- a/Lib/_pydecimal.py +++ b/Lib/_pydecimal.py @@ -1026,11 +1026,9 @@ class Decimal(object): """ if self._is_special: if self.is_nan(): - raise ValueError("Cannot pass NaN " - "to decimal.as_integer_ratio.") + raise ValueError("cannot convert NaN to integer ratio") else: - raise OverflowError("Cannot pass infinity " - "to decimal.as_integer_ratio.") + raise OverflowError("cannot convert Infinity to integer ratio") if not self: return 0, 1 diff --git a/Lib/fractions.py b/Lib/fractions.py index 60b07288070..64d746b842e 100644 --- a/Lib/fractions.py +++ b/Lib/fractions.py @@ -125,17 +125,9 @@ class Fraction(numbers.Rational): 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 + elif isinstance(numerator, (float, Decimal)): + # Exact conversion + self._numerator, self._denominator = numerator.as_integer_ratio() return self elif isinstance(numerator, str): @@ -210,10 +202,6 @@ class Fraction(numbers.Rational): elif not isinstance(f, float): raise TypeError("%s.from_float() only takes floats, not %r (%s)" % (cls.__name__, f, type(f).__name__)) - if math.isnan(f): - raise ValueError("Cannot convert %r to %s." % (f, cls.__name__)) - if math.isinf(f): - raise OverflowError("Cannot convert %r to %s." % (f, cls.__name__)) return cls(*f.as_integer_ratio()) @classmethod @@ -226,19 +214,7 @@ class Fraction(numbers.Rational): raise TypeError( "%s.from_decimal() only takes Decimals, not %r (%s)" % (cls.__name__, dec, type(dec).__name__)) - if dec.is_infinite(): - raise OverflowError( - "Cannot convert %s to %s." % (dec, cls.__name__)) - if dec.is_nan(): - raise ValueError("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) + return cls(*dec.as_integer_ratio()) def limit_denominator(self, max_denominator=1000000): """Closest Fraction to self with denominator at most max_denominator. diff --git a/Lib/test/test_fractions.py b/Lib/test/test_fractions.py index 16998522160..73d2dd3031e 100644 --- a/Lib/test/test_fractions.py +++ b/Lib/test/test_fractions.py @@ -263,13 +263,13 @@ class FractionTest(unittest.TestCase): nan = inf - inf # bug 16469: error types should be consistent with float -> int self.assertRaisesMessage( - OverflowError, "Cannot convert inf to Fraction.", + OverflowError, "cannot convert Infinity to integer ratio", F.from_float, inf) self.assertRaisesMessage( - OverflowError, "Cannot convert -inf to Fraction.", + OverflowError, "cannot convert Infinity to integer ratio", F.from_float, -inf) self.assertRaisesMessage( - ValueError, "Cannot convert nan to Fraction.", + ValueError, "cannot convert NaN to integer ratio", F.from_float, nan) def testFromDecimal(self): @@ -284,16 +284,16 @@ class FractionTest(unittest.TestCase): # bug 16469: error types should be consistent with decimal -> int self.assertRaisesMessage( - OverflowError, "Cannot convert Infinity to Fraction.", + OverflowError, "cannot convert Infinity to integer ratio", F.from_decimal, Decimal("inf")) self.assertRaisesMessage( - OverflowError, "Cannot convert -Infinity to Fraction.", + OverflowError, "cannot convert Infinity to integer ratio", F.from_decimal, Decimal("-inf")) self.assertRaisesMessage( - ValueError, "Cannot convert NaN to Fraction.", + ValueError, "cannot convert NaN to integer ratio", F.from_decimal, Decimal("nan")) self.assertRaisesMessage( - ValueError, "Cannot convert sNaN to Fraction.", + ValueError, "cannot convert NaN to integer ratio", F.from_decimal, Decimal("snan")) def testLimitDenominator(self): diff --git a/Misc/NEWS b/Misc/NEWS index 6ec585ac073..6333401c6ab 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -126,6 +126,9 @@ Core and Builtins Library ------- +- Issue #25971: Optimized creating Fractions from floats by 2 times and from + Decimals by 3 times. + - Issue #25802: Document as deprecated the remaining implementations of importlib.abc.Loader.load_module(). diff --git a/Objects/floatobject.c b/Objects/floatobject.c index d92bec35b5f..2949174c99d 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -1466,14 +1466,14 @@ float_as_integer_ratio(PyObject *v, PyObject *unused) CONVERT_TO_DOUBLE(v, self); if (Py_IS_INFINITY(self)) { - PyErr_SetString(PyExc_OverflowError, - "Cannot pass infinity to float.as_integer_ratio."); - return NULL; + PyErr_SetString(PyExc_OverflowError, + "cannot convert Infinity to integer ratio"); + return NULL; } if (Py_IS_NAN(self)) { - PyErr_SetString(PyExc_ValueError, - "Cannot pass NaN to float.as_integer_ratio."); - return NULL; + PyErr_SetString(PyExc_ValueError, + "cannot convert NaN to integer ratio"); + return NULL; } PyFPE_START_PROTECT("as_integer_ratio", goto error);