From 482b1f983e466f2834c739c6247ff555f5b5cfeb Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sun, 11 Aug 2019 16:55:59 +0300 Subject: [PATCH 1/2] bpo-37822: Add math.as_integer_ratio(). --- Doc/library/math.rst | 11 +++ Doc/whatsnew/3.9.rst | 8 ++ Lib/fractions.py | 26 ++----- Lib/statistics.py | 18 +---- Lib/test/test_math.py | 29 ++++++++ .../2019-08-11-16-55-46.bpo-37822.7xHl1w.rst | 1 + Modules/clinic/mathmodule.c.h | 11 ++- Modules/mathmodule.c | 74 +++++++++++++++++++ 8 files changed, 141 insertions(+), 37 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2019-08-11-16-55-46.bpo-37822.7xHl1w.rst diff --git a/Doc/library/math.rst b/Doc/library/math.rst index 43eaba935a164a..bbc577617a9473 100644 --- a/Doc/library/math.rst +++ b/Doc/library/math.rst @@ -29,6 +29,17 @@ noted otherwise, all return values are floats. Number-theoretic and representation functions --------------------------------------------- +.. function:: as_integer_ratio(number) + + Return integer ratio. + + This function returns ``number.as_integer_ratio()`` if the argument has + the ``as_integer_ratio()`` method, otherwise it returns a pair + ``(number.numerator, number.denominator)``. + + .. versionadded:: 3.9 + + .. function:: ceil(x) Return the ceiling of *x*, the smallest integer greater than or equal to *x*. diff --git a/Doc/whatsnew/3.9.rst b/Doc/whatsnew/3.9.rst index 61d9e745e87ccf..acdd418c08b2b7 100644 --- a/Doc/whatsnew/3.9.rst +++ b/Doc/whatsnew/3.9.rst @@ -109,6 +109,14 @@ New Modules Improved Modules ================ + +math +---- + +Added new function :func:`math.as_integer_ratio`. +(Contributed by Serhiy Storchaka in :issue:``.) + + threading --------- diff --git a/Lib/fractions.py b/Lib/fractions.py index 7443bd3e0c6af9..9f1ff0a10cc4dd 100644 --- a/Lib/fractions.py +++ b/Lib/fractions.py @@ -115,22 +115,7 @@ def __new__(cls, numerator=0, denominator=None, *, _normalize=True): self = super(Fraction, cls).__new__(cls) if denominator is None: - if type(numerator) is int: - self._numerator = numerator - self._denominator = 1 - return self - - elif isinstance(numerator, numbers.Rational): - self._numerator = numerator.numerator - self._denominator = numerator.denominator - return self - - elif isinstance(numerator, (float, Decimal)): - # Exact conversion - self._numerator, self._denominator = numerator.as_integer_ratio() - return self - - elif isinstance(numerator, str): + if isinstance(numerator, str): # Handle construction from strings. m = _RATIONAL_FORMAT.match(numerator) if m is None: @@ -156,10 +141,13 @@ def __new__(cls, numerator=0, denominator=None, *, _normalize=True): denominator *= 10**-exp if m.group('sign') == '-': numerator = -numerator - else: - raise TypeError("argument should be a string " - "or a Rational instance") + try: + self._numerator, self._denominator = math.as_integer_ratio(numerator) + return self + except TypeError: + raise TypeError("argument should be a string " + "or a Rational instance") elif type(numerator) is int is type(denominator): pass # *very* normal case diff --git a/Lib/statistics.py b/Lib/statistics.py index 77291dd62cb90e..b11047cb703421 100644 --- a/Lib/statistics.py +++ b/Lib/statistics.py @@ -225,27 +225,11 @@ def _exact_ratio(x): x is expected to be an int, Fraction, Decimal or float. """ try: - # Optimise the common case of floats. We expect that the most often - # used numeric type will be builtin floats, so try to make this as - # fast as possible. - if type(x) is float or type(x) is Decimal: - return x.as_integer_ratio() - try: - # x may be an int, Fraction, or Integral ABC. - return (x.numerator, x.denominator) - except AttributeError: - try: - # x may be a float or Decimal subclass. - return x.as_integer_ratio() - except AttributeError: - # Just give up? - pass + return math.as_integer_ratio(x) except (OverflowError, ValueError): # float NAN or INF. assert not _isfinite(x) return (x, None) - msg = "can't convert type '{}' to numerator/denominator" - raise TypeError(msg.format(type(x).__name__)) def _convert(value, T): diff --git a/Lib/test/test_math.py b/Lib/test/test_math.py index c237bc1942e655..492e024250bb11 100644 --- a/Lib/test/test_math.py +++ b/Lib/test/test_math.py @@ -12,6 +12,8 @@ import random import struct import sys +from decimal import Decimal +from fractions import Fraction eps = 1E-05 @@ -293,6 +295,33 @@ def testAcosh(self): self.assertRaises(ValueError, math.acosh, NINF) self.assertTrue(math.isnan(math.acosh(NAN))) + def testAsIntegerRatio(self): + as_integer_ratio = math.as_integer_ratio + self.assertEqual(as_integer_ratio(0), (0, 1)) + self.assertEqual(as_integer_ratio(3), (3, 1)) + self.assertEqual(as_integer_ratio(-3), (-3, 1)) + self.assertEqual(as_integer_ratio(False), (0, 1)) + self.assertEqual(as_integer_ratio(True), (1, 1)) + self.assertEqual(as_integer_ratio(0.0), (0, 1)) + self.assertEqual(as_integer_ratio(-0.0), (0, 1)) + self.assertEqual(as_integer_ratio(0.875), (7, 8)) + self.assertEqual(as_integer_ratio(-0.875), (-7, 8)) + self.assertEqual(as_integer_ratio(Decimal('0')), (0, 1)) + self.assertEqual(as_integer_ratio(Decimal('0.875')), (7, 8)) + self.assertEqual(as_integer_ratio(Decimal('-0.875')), (-7, 8)) + self.assertEqual(as_integer_ratio(Fraction(0)), (0, 1)) + self.assertEqual(as_integer_ratio(Fraction(7, 8)), (7, 8)) + self.assertEqual(as_integer_ratio(Fraction(-7, 8)), (-7, 8)) + + self.assertRaises(OverflowError, as_integer_ratio, float('inf')) + self.assertRaises(OverflowError, as_integer_ratio, float('-inf')) + self.assertRaises(ValueError, as_integer_ratio, float('nan')) + self.assertRaises(OverflowError, as_integer_ratio, Decimal('inf')) + self.assertRaises(OverflowError, as_integer_ratio, Decimal('-inf')) + self.assertRaises(ValueError, as_integer_ratio, Decimal('nan')) + + self.assertRaises(TypeError, as_integer_ratio, '0') + def testAsin(self): self.assertRaises(TypeError, math.asin) self.ftest('asin(-1)', math.asin(-1), -math.pi/2) diff --git a/Misc/NEWS.d/next/Library/2019-08-11-16-55-46.bpo-37822.7xHl1w.rst b/Misc/NEWS.d/next/Library/2019-08-11-16-55-46.bpo-37822.7xHl1w.rst new file mode 100644 index 00000000000000..37e9c819b3bb21 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-08-11-16-55-46.bpo-37822.7xHl1w.rst @@ -0,0 +1 @@ +Added :func:`math.as_integer_ratio`. diff --git a/Modules/clinic/mathmodule.c.h b/Modules/clinic/mathmodule.c.h index 84561b955787b7..701e1c932b83f2 100644 --- a/Modules/clinic/mathmodule.c.h +++ b/Modules/clinic/mathmodule.c.h @@ -712,4 +712,13 @@ math_comb(PyObject *module, PyObject *const *args, Py_ssize_t nargs) exit: return return_value; } -/*[clinic end generated code: output=f93cfe13ab2fdb4e input=a9049054013a1b77]*/ + +PyDoc_STRVAR(math_as_integer_ratio__doc__, +"as_integer_ratio($module, x, /)\n" +"--\n" +"\n" +"greatest common divisor of x and y"); + +#define MATH_AS_INTEGER_RATIO_METHODDEF \ + {"as_integer_ratio", (PyCFunction)math_as_integer_ratio, METH_O, math_as_integer_ratio__doc__}, +/*[clinic end generated code: output=f9722232fc321d17 input=a9049054013a1b77]*/ diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c index e1b46ec384a373..76bc1cc1c9ed4f 100644 --- a/Modules/mathmodule.c +++ b/Modules/mathmodule.c @@ -3306,9 +3306,83 @@ math_comb_impl(PyObject *module, PyObject *n, PyObject *k) } +/*[clinic input] +math.as_integer_ratio + x: object + / +greatest common divisor of x and y +[clinic start generated code]*/ + +static PyObject * +math_as_integer_ratio(PyObject *module, PyObject *x) +/*[clinic end generated code: output=1844868fd4efb2f1 input=d7f2e8ffd51c6599]*/ +{ + _Py_IDENTIFIER(as_integer_ratio); + _Py_IDENTIFIER(numerator); + _Py_IDENTIFIER(denominator); + PyObject *ratio, *as_integer_ratio, *numerator, *denominator; + + if (PyLong_CheckExact(x)) { + return PyTuple_Pack(2, x, _PyLong_One); + } + + if (_PyObject_LookupAttrId(x, &PyId_as_integer_ratio, &as_integer_ratio) < 0) { + return NULL; + } + if (as_integer_ratio) { + ratio = _PyObject_CallNoArg(as_integer_ratio); + Py_DECREF(as_integer_ratio); + if (ratio == NULL) { + return NULL; + } + if (!PyTuple_Check(ratio)) { + PyErr_Format(PyExc_TypeError, + "unexpected return type from as_integer_ratio(): " + "expected tuple, got '%.200s'", + Py_TYPE(ratio)->tp_name); + Py_DECREF(ratio); + return NULL; + } + if (PyTuple_GET_SIZE(ratio) != 2) { + PyErr_SetString(PyExc_ValueError, + "as_integer_ratio() must return a 2-tuple"); + Py_DECREF(ratio); + return NULL; + } + } + else { + if (_PyObject_LookupAttrId(x, &PyId_numerator, &numerator) < 0) { + return NULL; + } + if (numerator == NULL) { + PyErr_Format(PyExc_TypeError, + "required a number, not '%.200s'", + Py_TYPE(x)->tp_name); + return NULL; + } + if (_PyObject_LookupAttrId(x, &PyId_denominator, &denominator) < 0) { + Py_DECREF(numerator); + return NULL; + } + if (denominator == NULL) { + Py_DECREF(numerator); + PyErr_Format(PyExc_TypeError, + "required a number, not '%.200s'", + Py_TYPE(x)->tp_name); + return NULL; + } + ratio = PyTuple_Pack(2, numerator, denominator); + Py_DECREF(numerator); + Py_DECREF(denominator); + } + return ratio; +} + + static PyMethodDef math_methods[] = { {"acos", math_acos, METH_O, math_acos_doc}, {"acosh", math_acosh, METH_O, math_acosh_doc}, + MATH_AS_INTEGER_RATIO_METHODDEF {"asin", math_asin, METH_O, math_asin_doc}, {"asinh", math_asinh, METH_O, math_asinh_doc}, {"atan", math_atan, METH_O, math_atan_doc}, From a587d7d890f137f196ea2de650d324889d5eac5d Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sun, 11 Aug 2019 17:37:25 +0300 Subject: [PATCH 2/2] Better TypeError message. --- Lib/fractions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/fractions.py b/Lib/fractions.py index 9f1ff0a10cc4dd..05eda36b438dac 100644 --- a/Lib/fractions.py +++ b/Lib/fractions.py @@ -146,8 +146,8 @@ def __new__(cls, numerator=0, denominator=None, *, _normalize=True): self._numerator, self._denominator = math.as_integer_ratio(numerator) return self except TypeError: - raise TypeError("argument should be a string " - "or a Rational instance") + raise TypeError("argument should be a string or a number, " + "not %s" % type(numerator).__name__) from None elif type(numerator) is int is type(denominator): pass # *very* normal case pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy