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..05eda36b438dac 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 number, " + "not %s" % type(numerator).__name__) from None 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}, 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