From 231aad38493c871dd32930a21d256cbacd2ae20c Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 17 Jun 2019 16:57:27 +0300 Subject: [PATCH] bpo-37315: Deprecate accepting floats in math.factorial(). (GH-14147) --- Doc/library/math.rst | 3 +++ Doc/whatsnew/3.9.rst | 5 +++++ Lib/test/test_math.py | 19 ++++++++++++------- .../2019-06-17-11-59-52.bpo-37315.o1xFC0.rst | 2 ++ Modules/mathmodule.c | 6 ++++++ 5 files changed, 28 insertions(+), 7 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2019-06-17-11-59-52.bpo-37315.o1xFC0.rst diff --git a/Doc/library/math.rst b/Doc/library/math.rst index ff937d27c6c..bfce41a7f4c 100644 --- a/Doc/library/math.rst +++ b/Doc/library/math.rst @@ -71,6 +71,9 @@ Number-theoretic and representation functions Return *x* factorial as an integer. Raises :exc:`ValueError` if *x* is not integral or is negative. + .. deprecated:: 3.9 + Accepting floats with integral values (like ``5.0``) is deprecated. + .. function:: floor(x) diff --git a/Doc/whatsnew/3.9.rst b/Doc/whatsnew/3.9.rst index 62b013f7721..c5cb626a1b6 100644 --- a/Doc/whatsnew/3.9.rst +++ b/Doc/whatsnew/3.9.rst @@ -109,6 +109,11 @@ Build and C API Changes Deprecated ========== +* Currently :func:`math.factorial` accepts :class:`float` instances with + non-negative integer values (like ``5.0``). It raises a :exc:`ValueError` + for non-integral and negative floats. It is deprecated now. In future + Python versions it will raise a :exc:`TypeError` for all floats. + (Contributed by Serhiy Storchaka in :issue:`37315`.) Removed diff --git a/Lib/test/test_math.py b/Lib/test/test_math.py index adefa07a404..f25913941b8 100644 --- a/Lib/test/test_math.py +++ b/Lib/test/test_math.py @@ -501,21 +501,25 @@ class MathTests(unittest.TestCase): def testFactorial(self): self.assertEqual(math.factorial(0), 1) - self.assertEqual(math.factorial(0.0), 1) total = 1 for i in range(1, 1000): total *= i self.assertEqual(math.factorial(i), total) - self.assertEqual(math.factorial(float(i)), total) self.assertEqual(math.factorial(i), py_factorial(i)) self.assertRaises(ValueError, math.factorial, -1) - self.assertRaises(ValueError, math.factorial, -1.0) self.assertRaises(ValueError, math.factorial, -10**100) - self.assertRaises(ValueError, math.factorial, -1e100) - self.assertRaises(ValueError, math.factorial, math.pi) def testFactorialNonIntegers(self): - self.assertRaises(TypeError, math.factorial, decimal.Decimal(5.2)) + with self.assertWarns(DeprecationWarning): + self.assertEqual(math.factorial(5.0), 120) + with self.assertWarns(DeprecationWarning): + self.assertRaises(ValueError, math.factorial, 5.2) + with self.assertWarns(DeprecationWarning): + self.assertRaises(ValueError, math.factorial, -1.0) + with self.assertWarns(DeprecationWarning): + self.assertRaises(ValueError, math.factorial, -1e100) + self.assertRaises(TypeError, math.factorial, decimal.Decimal('5')) + self.assertRaises(TypeError, math.factorial, decimal.Decimal('5.2')) self.assertRaises(TypeError, math.factorial, "5") # Other implementations may place different upper bounds. @@ -524,7 +528,8 @@ class MathTests(unittest.TestCase): # Currently raises ValueError for inputs that are too large # to fit into a C long. self.assertRaises(OverflowError, math.factorial, 10**100) - self.assertRaises(OverflowError, math.factorial, 1e100) + with self.assertWarns(DeprecationWarning): + self.assertRaises(OverflowError, math.factorial, 1e100) def testFloor(self): self.assertRaises(TypeError, math.floor) diff --git a/Misc/NEWS.d/next/Library/2019-06-17-11-59-52.bpo-37315.o1xFC0.rst b/Misc/NEWS.d/next/Library/2019-06-17-11-59-52.bpo-37315.o1xFC0.rst new file mode 100644 index 00000000000..fd5981989b7 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-06-17-11-59-52.bpo-37315.o1xFC0.rst @@ -0,0 +1,2 @@ +Deprecated accepting floats with integral value (like ``5.0``) in +:func:`math.factorial`. diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c index 82a9a14724f..a75a3c929e7 100644 --- a/Modules/mathmodule.c +++ b/Modules/mathmodule.c @@ -1981,6 +1981,12 @@ math_factorial(PyObject *module, PyObject *arg) PyObject *result, *odd_part, *pyint_form; if (PyFloat_Check(arg)) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "Using factorial() with floats is deprecated", + 1) < 0) + { + return NULL; + } PyObject *lx; double dx = PyFloat_AS_DOUBLE((PyFloatObject *)arg); if (!(Py_IS_FINITE(dx) && dx == floor(dx))) {