From 4511c7a3f43d7c6349d18cf257909dc4130d7234 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sat, 1 Jun 2019 22:54:26 +0300 Subject: [PATCH 1/5] bpo-35431: Add math.perm(). --- Doc/library/math.rst | 42 +++-- Lib/test/test_math.py | 55 +++++++ .../2019-06-01-22-54-03.bpo-35431.oGXBWN.rst | 1 + Modules/clinic/mathmodule.c.h | 37 ++++- Modules/mathmodule.c | 149 ++++++++++++------ 5 files changed, 223 insertions(+), 61 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2019-06-01-22-54-03.bpo-35431.oGXBWN.rst diff --git a/Doc/library/math.rst b/Doc/library/math.rst index 206b06edd2a206..61dffe09569764 100644 --- a/Doc/library/math.rst +++ b/Doc/library/math.rst @@ -36,6 +36,21 @@ Number-theoretic and representation functions :class:`~numbers.Integral` value. +.. function:: comb(n, k) + + Return the number of ways to choose *k* items from *n* items without repetition + and without order. + + Also called the binomial coefficient. It is mathematically equal to the expression + ``n! / (k! (n - k)!)``. It is equivalent to the coefficient of the *k*-th term in the + polynomial expansion of the expression ``(1 + x) ** n``. + + Raises :exc:`TypeError` if the arguments not integers. + Raises :exc:`ValueError` if the arguments are negative or if *k* > *n*. + + .. versionadded:: 3.8 + + .. function:: copysign(x, y) Return a float with the magnitude (absolute value) of *x* but the sign of @@ -192,6 +207,18 @@ Number-theoretic and representation functions of *x* and are floats. +.. function:: perm(n, k) + + Return the number of ways to choose *k* items from *n* items without repetition. + + It is mathematically equal to the expression ``n! / (n - k)!``. + + Raises :exc:`TypeError` if the arguments not integers. + Raises :exc:`ValueError` if the arguments are negative or if *k* > *n*. + + .. versionadded:: 3.8 + + .. function:: prod(iterable, *, start=1) Calculate the product of all the elements in the input *iterable*. @@ -232,21 +259,6 @@ Number-theoretic and representation functions :meth:`x.__trunc__() `. -.. function:: comb(n, k) - - Return the number of ways to choose *k* items from *n* items without repetition - and without order. - - Also called the binomial coefficient. It is mathematically equal to the expression - ``n! / (k! (n - k)!)``. It is equivalent to the coefficient of the *k*-th term in the - polynomial expansion of the expression ``(1 + x) ** n``. - - Raises :exc:`TypeError` if the arguments not integers. - Raises :exc:`ValueError` if the arguments are negative or if *k* > *n*. - - .. versionadded:: 3.8 - - Note that :func:`frexp` and :func:`modf` have a different call/return pattern than their C equivalents: they take a single argument and return a pair of values, rather than returning their second return value through an 'output diff --git a/Lib/test/test_math.py b/Lib/test/test_math.py index e27092eefd6ed7..c595f02a790e26 100644 --- a/Lib/test/test_math.py +++ b/Lib/test/test_math.py @@ -1862,6 +1862,61 @@ def test_fractions(self): self.assertAllClose(fraction_examples, rel_tol=1e-8) self.assertAllNotClose(fraction_examples, rel_tol=1e-9) + def testPerm(self): + perm = math.perm + factorial = math.factorial + # Test if factorial defintion is satisfied + for n in range(100): + for k in range(n + 1): + self.assertEqual(perm(n, k), + factorial(n) // factorial(n - k)) + + # Test for Pascal's identity + for n in range(1, 100): + for k in range(1, n): + self.assertEqual(perm(n, k), perm(n - 1, k - 1) * k + perm(n - 1, k)) + + # Test corner cases + for n in range(1, 100): + self.assertEqual(perm(n, 0), 1) + self.assertEqual(perm(n, 1), n) + self.assertEqual(perm(n, n), factorial(n)) + + # Raises TypeError if any argument is non-integer or argument count is + # not 2 + self.assertRaises(TypeError, perm, 10, 1.0) + self.assertRaises(TypeError, perm, 10, decimal.Decimal(1.0)) + self.assertRaises(TypeError, perm, 10, "1") + self.assertRaises(TypeError, perm, 10.0, 1) + self.assertRaises(TypeError, perm, decimal.Decimal(10.0), 1) + self.assertRaises(TypeError, perm, "10", 1) + + self.assertRaises(TypeError, perm, 10) + self.assertRaises(TypeError, perm, 10, 1, 3) + self.assertRaises(TypeError, perm) + + # Raises Value error if not k or n are negative numbers + self.assertRaises(ValueError, perm, -1, 1) + self.assertRaises(ValueError, perm, -2**1000, 1) + self.assertRaises(ValueError, perm, 1, -1) + self.assertRaises(ValueError, perm, 1, -2**1000) + + # Raises value error if k is greater than n + self.assertRaises(ValueError, perm, 1, 2) + self.assertRaises(ValueError, perm, 1, 2**1000) + + n = 2**1000 + self.assertEqual(perm(n, 0), 1) + self.assertEqual(perm(n, 1), n) + self.assertEqual(perm(n, 2), n * (n-1)) + self.assertRaises((OverflowError, MemoryError), perm, n, n) + + for n, k in (True, True), (True, False), (False, False): + self.assertEqual(perm(n, k), 1) + self.assertIs(type(perm(n, k)), int) + self.assertEqual(perm(MyIndexable(5), MyIndexable(2)), 20) + self.assertIs(type(perm(MyIndexable(5), MyIndexable(2))), int) + def testComb(self): comb = math.comb factorial = math.factorial diff --git a/Misc/NEWS.d/next/Library/2019-06-01-22-54-03.bpo-35431.oGXBWN.rst b/Misc/NEWS.d/next/Library/2019-06-01-22-54-03.bpo-35431.oGXBWN.rst new file mode 100644 index 00000000000000..f1b825890231d9 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-06-01-22-54-03.bpo-35431.oGXBWN.rst @@ -0,0 +1 @@ +Added :func:`math.perm`. diff --git a/Modules/clinic/mathmodule.c.h b/Modules/clinic/mathmodule.c.h index 92ec4bec9bf17d..bd079ac0ee9167 100644 --- a/Modules/clinic/mathmodule.c.h +++ b/Modules/clinic/mathmodule.c.h @@ -638,6 +638,41 @@ math_prod(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *k return return_value; } +PyDoc_STRVAR(math_perm__doc__, +"perm($module, n, k, /)\n" +"--\n" +"\n" +"Number of ways to choose k items from n items without repetition.\n" +"\n" +"It is mathematically equal to the expression n! / (n - k)!.\n" +"\n" +"Raises TypeError if the arguments are not integers.\n" +"Raises ValueError if the arguments are negative or if k > n."); + +#define MATH_PERM_METHODDEF \ + {"perm", (PyCFunction)(void(*)(void))math_perm, METH_FASTCALL, math_perm__doc__}, + +static PyObject * +math_perm_impl(PyObject *module, PyObject *n, PyObject *k); + +static PyObject * +math_perm(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + PyObject *n; + PyObject *k; + + if (!_PyArg_CheckPositional("perm", nargs, 2, 2)) { + goto exit; + } + n = args[0]; + k = args[1]; + return_value = math_perm_impl(module, n, k); + +exit: + return return_value; +} + PyDoc_STRVAR(math_comb__doc__, "comb($module, n, k, /)\n" "--\n" @@ -674,4 +709,4 @@ math_comb(PyObject *module, PyObject *const *args, Py_ssize_t nargs) exit: return return_value; } -/*[clinic end generated code: output=6709521e5e1d90ec input=a9049054013a1b77]*/ +/*[clinic end generated code: output=64b3f83fa16bf67b input=a9049054013a1b77]*/ diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c index bea4607b9be1ee..cd376691fcb7d8 100644 --- a/Modules/mathmodule.c +++ b/Modules/mathmodule.c @@ -2998,27 +2998,8 @@ math_prod_impl(PyObject *module, PyObject *iterable, PyObject *start) } -/*[clinic input] -math.comb - - n: object - k: object - / - -Number of ways to choose k items from n items without repetition and without order. - -Also called the binomial coefficient. It is mathematically equal to the expression -n! / (k! * (n - k)!). It is equivalent to the coefficient of k-th term in -polynomial expansion of the expression (1 + x)**n. - -Raises TypeError if the arguments are not integers. -Raises ValueError if the arguments are negative or if k > n. - -[clinic start generated code]*/ - static PyObject * -math_comb_impl(PyObject *module, PyObject *n, PyObject *k) -/*[clinic end generated code: output=bd2cec8d854f3493 input=2f336ac9ec8242f9]*/ +perm_comb(PyObject *n, PyObject *k, int comb) { PyObject *result = NULL, *factor = NULL, *temp; int overflow, cmp; @@ -3028,35 +3009,60 @@ math_comb_impl(PyObject *module, PyObject *n, PyObject *k) if (n == NULL) { return NULL; } + if (!PyLong_CheckExact(n)) { + Py_SETREF(n, _PyLong_Copy((PyLongObject *)n)); + if (n == NULL) { + return NULL; + } + } k = PyNumber_Index(k); if (k == NULL) { Py_DECREF(n); return NULL; } + if (!PyLong_CheckExact(k)) { + Py_SETREF(k, _PyLong_Copy((PyLongObject *)k)); + if (k == NULL) { + Py_DECREF(n); + return NULL; + } + } if (Py_SIZE(n) < 0) { PyErr_SetString(PyExc_ValueError, "n must be a non-negative integer"); goto error; } - /* k = min(k, n - k) */ - temp = PyNumber_Subtract(n, k); - if (temp == NULL) { - goto error; - } - if (Py_SIZE(temp) < 0) { - Py_DECREF(temp); - PyErr_SetString(PyExc_ValueError, - "k must be an integer less than or equal to n"); - goto error; - } - cmp = PyObject_RichCompareBool(k, temp, Py_GT); - if (cmp > 0) { - Py_SETREF(k, temp); + if (comb) { + /* k = min(k, n - k) */ + temp = PyNumber_Subtract(n, k); + if (temp == NULL) { + goto error; + } + if (Py_SIZE(temp) < 0) { + Py_DECREF(temp); + PyErr_SetString(PyExc_ValueError, + "k must be an integer less than or equal to n"); + goto error; + } + cmp = PyObject_RichCompareBool(temp, k, Py_LT); + if (cmp > 0) { + Py_SETREF(k, temp); + } + else { + Py_DECREF(temp); + if (cmp < 0) { + goto error; + } + } } else { - Py_DECREF(temp); - if (cmp < 0) { + cmp = PyObject_RichCompareBool(n, k, Py_LT); + if (cmp != 0) { + if (cmp > 0) { + PyErr_SetString(PyExc_ValueError, + "k must be an integer less than or equal to n"); + } goto error; } } @@ -3064,7 +3070,8 @@ math_comb_impl(PyObject *module, PyObject *n, PyObject *k) factors = PyLong_AsLongLongAndOverflow(k, &overflow); if (overflow > 0) { PyErr_Format(PyExc_OverflowError, - "min(n - k, k) must not exceed %lld", + "%s must not exceed %lld", + comb ? "min(n - k, k)" : "k", LLONG_MAX); goto error; } @@ -3099,14 +3106,16 @@ math_comb_impl(PyObject *module, PyObject *n, PyObject *k) goto error; } - temp = PyLong_FromUnsignedLongLong((unsigned long long)i + 1); - if (temp == NULL) { - goto error; - } - Py_SETREF(result, PyNumber_FloorDivide(result, temp)); - Py_DECREF(temp); - if (result == NULL) { - goto error; + if (comb) { + temp = PyLong_FromUnsignedLongLong((unsigned long long)i + 1); + if (temp == NULL) { + goto error; + } + Py_SETREF(result, PyNumber_FloorDivide(result, temp)); + Py_DECREF(temp); + if (result == NULL) { + goto error; + } } } Py_DECREF(factor); @@ -3125,6 +3134,55 @@ math_comb_impl(PyObject *module, PyObject *n, PyObject *k) } +/*[clinic input] +math.perm + + n: object + k: object + / + +Number of ways to choose k items from n items without repetition. + +It is mathematically equal to the expression n! / (n - k)!. + +Raises TypeError if the arguments are not integers. +Raises ValueError if the arguments are negative or if k > n. +[clinic start generated code]*/ + +static PyObject * +math_perm_impl(PyObject *module, PyObject *n, PyObject *k) +/*[clinic end generated code: output=e021a25469653e23 input=bad86be85158ebfd]*/ +{ + return perm_comb(n, k, 0); +} + + +/*[clinic input] +math.comb + + n: object + k: object + / + +Number of ways to choose k items from n items without repetition and without order. + +Also called the binomial coefficient. It is mathematically equal to the expression +n! / (k! * (n - k)!). It is equivalent to the coefficient of k-th term in +polynomial expansion of the expression (1 + x)**n. + +Raises TypeError if the arguments are not integers. +Raises ValueError if the arguments are negative or if k > n. + +[clinic start generated code]*/ + +static PyObject * +math_comb_impl(PyObject *module, PyObject *n, PyObject *k) +/*[clinic end generated code: output=bd2cec8d854f3493 input=2f336ac9ec8242f9]*/ +{ + return perm_comb(n, k, 1); +} + + static PyMethodDef math_methods[] = { {"acos", math_acos, METH_O, math_acos_doc}, {"acosh", math_acosh, METH_O, math_acosh_doc}, @@ -3174,6 +3232,7 @@ static PyMethodDef math_methods[] = { {"tanh", math_tanh, METH_O, math_tanh_doc}, MATH_TRUNC_METHODDEF MATH_PROD_METHODDEF + MATH_PERM_METHODDEF MATH_COMB_METHODDEF {NULL, NULL} /* sentinel */ }; From 5b9aac0486089c9dfd5749b4c30aa723a0592fce Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sat, 1 Jun 2019 23:48:37 +0300 Subject: [PATCH 2/5] Opened new issue 37128. --- ...-35431.oGXBWN.rst => 2019-06-01-22-54-03.bpo-37128.oGXBWN.rst} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Misc/NEWS.d/next/Library/{2019-06-01-22-54-03.bpo-35431.oGXBWN.rst => 2019-06-01-22-54-03.bpo-37128.oGXBWN.rst} (100%) diff --git a/Misc/NEWS.d/next/Library/2019-06-01-22-54-03.bpo-35431.oGXBWN.rst b/Misc/NEWS.d/next/Library/2019-06-01-22-54-03.bpo-37128.oGXBWN.rst similarity index 100% rename from Misc/NEWS.d/next/Library/2019-06-01-22-54-03.bpo-35431.oGXBWN.rst rename to Misc/NEWS.d/next/Library/2019-06-01-22-54-03.bpo-37128.oGXBWN.rst From 29cc2500e258f535d2ca6d83574b3921b3e00000 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sun, 2 Jun 2019 08:55:24 +0300 Subject: [PATCH 3/5] Fix docs. --- Doc/library/math.rst | 3 ++- Modules/clinic/mathmodule.c.h | 4 ++-- Modules/mathmodule.c | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Doc/library/math.rst b/Doc/library/math.rst index 61dffe09569764..0f11943845f161 100644 --- a/Doc/library/math.rst +++ b/Doc/library/math.rst @@ -209,7 +209,8 @@ Number-theoretic and representation functions .. function:: perm(n, k) - Return the number of ways to choose *k* items from *n* items without repetition. + Return the distinct number of ways to choose *k* items from *n* items + without repetition and with order. It is mathematically equal to the expression ``n! / (n - k)!``. diff --git a/Modules/clinic/mathmodule.c.h b/Modules/clinic/mathmodule.c.h index bd079ac0ee9167..0efe5cc409ceb1 100644 --- a/Modules/clinic/mathmodule.c.h +++ b/Modules/clinic/mathmodule.c.h @@ -642,7 +642,7 @@ PyDoc_STRVAR(math_perm__doc__, "perm($module, n, k, /)\n" "--\n" "\n" -"Number of ways to choose k items from n items without repetition.\n" +"Number of ways to choose k items from n items without repetition and with order.\n" "\n" "It is mathematically equal to the expression n! / (n - k)!.\n" "\n" @@ -709,4 +709,4 @@ math_comb(PyObject *module, PyObject *const *args, Py_ssize_t nargs) exit: return return_value; } -/*[clinic end generated code: output=64b3f83fa16bf67b input=a9049054013a1b77]*/ +/*[clinic end generated code: output=a82b0e705b6d0ec0 input=a9049054013a1b77]*/ diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c index cd376691fcb7d8..af3c1f5f6331f2 100644 --- a/Modules/mathmodule.c +++ b/Modules/mathmodule.c @@ -3141,7 +3141,7 @@ math.perm k: object / -Number of ways to choose k items from n items without repetition. +Number of ways to choose k items from n items without repetition and with order. It is mathematically equal to the expression n! / (n - k)!. @@ -3151,7 +3151,7 @@ Raises ValueError if the arguments are negative or if k > n. static PyObject * math_perm_impl(PyObject *module, PyObject *n, PyObject *k) -/*[clinic end generated code: output=e021a25469653e23 input=bad86be85158ebfd]*/ +/*[clinic end generated code: output=e021a25469653e23 input=f71ee4f6ff26be24]*/ { return perm_comb(n, k, 0); } From a6085085a9c7c5d2f91fe25f75dd15fbb46099fd Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sun, 2 Jun 2019 08:58:37 +0300 Subject: [PATCH 4/5] Decouple perm() and comb() implementations. --- Modules/mathmodule.c | 207 ++++++++++++++++++++++++++++--------------- 1 file changed, 138 insertions(+), 69 deletions(-) diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c index af3c1f5f6331f2..6e1099321c5495 100644 --- a/Modules/mathmodule.c +++ b/Modules/mathmodule.c @@ -2998,10 +2998,26 @@ math_prod_impl(PyObject *module, PyObject *iterable, PyObject *start) } +/*[clinic input] +math.perm + + n: object + k: object + / + +Number of ways to choose k items from n items without repetition and with order. + +It is mathematically equal to the expression n! / (n - k)!. + +Raises TypeError if the arguments are not integers. +Raises ValueError if the arguments are negative or if k > n. +[clinic start generated code]*/ + static PyObject * -perm_comb(PyObject *n, PyObject *k, int comb) +math_perm_impl(PyObject *module, PyObject *n, PyObject *k) +/*[clinic end generated code: output=e021a25469653e23 input=f71ee4f6ff26be24]*/ { - PyObject *result = NULL, *factor = NULL, *temp; + PyObject *result = NULL, *factor = NULL; int overflow, cmp; long long i, factors; @@ -3033,45 +3049,19 @@ perm_comb(PyObject *n, PyObject *k, int comb) "n must be a non-negative integer"); goto error; } - if (comb) { - /* k = min(k, n - k) */ - temp = PyNumber_Subtract(n, k); - if (temp == NULL) { - goto error; - } - if (Py_SIZE(temp) < 0) { - Py_DECREF(temp); + cmp = PyObject_RichCompareBool(n, k, Py_LT); + if (cmp != 0) { + if (cmp > 0) { PyErr_SetString(PyExc_ValueError, "k must be an integer less than or equal to n"); - goto error; - } - cmp = PyObject_RichCompareBool(temp, k, Py_LT); - if (cmp > 0) { - Py_SETREF(k, temp); - } - else { - Py_DECREF(temp); - if (cmp < 0) { - goto error; - } - } - } - else { - cmp = PyObject_RichCompareBool(n, k, Py_LT); - if (cmp != 0) { - if (cmp > 0) { - PyErr_SetString(PyExc_ValueError, - "k must be an integer less than or equal to n"); - } - goto error; } + goto error; } factors = PyLong_AsLongLongAndOverflow(k, &overflow); if (overflow > 0) { PyErr_Format(PyExc_OverflowError, - "%s must not exceed %lld", - comb ? "min(n - k, k)" : "k", + "k must not exceed %lld", LLONG_MAX); goto error; } @@ -3105,18 +3095,6 @@ perm_comb(PyObject *n, PyObject *k, int comb) if (result == NULL) { goto error; } - - if (comb) { - temp = PyLong_FromUnsignedLongLong((unsigned long long)i + 1); - if (temp == NULL) { - goto error; - } - Py_SETREF(result, PyNumber_FloorDivide(result, temp)); - Py_DECREF(temp); - if (result == NULL) { - goto error; - } - } } Py_DECREF(factor); @@ -3134,29 +3112,6 @@ perm_comb(PyObject *n, PyObject *k, int comb) } -/*[clinic input] -math.perm - - n: object - k: object - / - -Number of ways to choose k items from n items without repetition and with order. - -It is mathematically equal to the expression n! / (n - k)!. - -Raises TypeError if the arguments are not integers. -Raises ValueError if the arguments are negative or if k > n. -[clinic start generated code]*/ - -static PyObject * -math_perm_impl(PyObject *module, PyObject *n, PyObject *k) -/*[clinic end generated code: output=e021a25469653e23 input=f71ee4f6ff26be24]*/ -{ - return perm_comb(n, k, 0); -} - - /*[clinic input] math.comb @@ -3179,7 +3134,121 @@ static PyObject * math_comb_impl(PyObject *module, PyObject *n, PyObject *k) /*[clinic end generated code: output=bd2cec8d854f3493 input=2f336ac9ec8242f9]*/ { - return perm_comb(n, k, 1); + PyObject *result = NULL, *factor = NULL, *temp; + int overflow, cmp; + long long i, factors; + + n = PyNumber_Index(n); + if (n == NULL) { + return NULL; + } + if (!PyLong_CheckExact(n)) { + Py_SETREF(n, _PyLong_Copy((PyLongObject *)n)); + if (n == NULL) { + return NULL; + } + } + k = PyNumber_Index(k); + if (k == NULL) { + Py_DECREF(n); + return NULL; + } + if (!PyLong_CheckExact(k)) { + Py_SETREF(k, _PyLong_Copy((PyLongObject *)k)); + if (k == NULL) { + Py_DECREF(n); + return NULL; + } + } + + if (Py_SIZE(n) < 0) { + PyErr_SetString(PyExc_ValueError, + "n must be a non-negative integer"); + goto error; + } + /* k = min(k, n - k) */ + temp = PyNumber_Subtract(n, k); + if (temp == NULL) { + goto error; + } + if (Py_SIZE(temp) < 0) { + Py_DECREF(temp); + PyErr_SetString(PyExc_ValueError, + "k must be an integer less than or equal to n"); + goto error; + } + cmp = PyObject_RichCompareBool(temp, k, Py_LT); + if (cmp > 0) { + Py_SETREF(k, temp); + } + else { + Py_DECREF(temp); + if (cmp < 0) { + goto error; + } + } + + factors = PyLong_AsLongLongAndOverflow(k, &overflow); + if (overflow > 0) { + PyErr_Format(PyExc_OverflowError, + "min(n - k, k) must not exceed %lld", + LLONG_MAX); + goto error; + } + else if (overflow < 0 || factors < 0) { + if (!PyErr_Occurred()) { + PyErr_SetString(PyExc_ValueError, + "k must be a non-negative integer"); + } + goto error; + } + + if (factors == 0) { + result = PyLong_FromLong(1); + goto done; + } + + result = n; + Py_INCREF(result); + if (factors == 1) { + goto done; + } + + factor = n; + Py_INCREF(factor); + for (i = 1; i < factors; ++i) { + Py_SETREF(factor, PyNumber_Subtract(factor, _PyLong_One)); + if (factor == NULL) { + goto error; + } + Py_SETREF(result, PyNumber_Multiply(result, factor)); + if (result == NULL) { + goto error; + } + + temp = PyLong_FromUnsignedLongLong((unsigned long long)i + 1); + if (temp == NULL) { + goto error; + } + Py_SETREF(result, PyNumber_FloorDivide(result, temp)); + Py_DECREF(temp); + if (result == NULL) { + goto error; + } + } + Py_DECREF(factor); + +done: + Py_DECREF(n); + Py_DECREF(k); + return result; + +error: + Py_XDECREF(factor); + Py_XDECREF(result); + Py_DECREF(n); + Py_DECREF(k); + return NULL; } From a75d23251683deec41dc464ec6ec392a4cbcf62c Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sun, 2 Jun 2019 10:57:21 +0300 Subject: [PATCH 5/5] Tweak docs and add tests for int subclass. --- Doc/library/math.rst | 2 +- Lib/test/test_math.py | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/Doc/library/math.rst b/Doc/library/math.rst index 0f11943845f161..c5a77f1fab9fd6 100644 --- a/Doc/library/math.rst +++ b/Doc/library/math.rst @@ -209,7 +209,7 @@ Number-theoretic and representation functions .. function:: perm(n, k) - Return the distinct number of ways to choose *k* items from *n* items + Return the number of ways to choose *k* items from *n* items without repetition and with order. It is mathematically equal to the expression ``n! / (n - k)!``. diff --git a/Lib/test/test_math.py b/Lib/test/test_math.py index c595f02a790e26..96e0cf2fe67197 100644 --- a/Lib/test/test_math.py +++ b/Lib/test/test_math.py @@ -240,6 +240,9 @@ def result_check(expected, got, ulp_tol=5, abs_tol=0.0): else: return None +class IntSubclass(int): + pass + # Class providing an __index__ method. class MyIndexable(object): def __init__(self, value): @@ -1914,8 +1917,11 @@ def testPerm(self): for n, k in (True, True), (True, False), (False, False): self.assertEqual(perm(n, k), 1) self.assertIs(type(perm(n, k)), int) + self.assertEqual(perm(IntSubclass(5), IntSubclass(2)), 20) self.assertEqual(perm(MyIndexable(5), MyIndexable(2)), 20) - self.assertIs(type(perm(MyIndexable(5), MyIndexable(2))), int) + for k in range(3): + self.assertIs(type(perm(IntSubclass(5), IntSubclass(k))), int) + self.assertIs(type(perm(MyIndexable(5), MyIndexable(k))), int) def testComb(self): comb = math.comb @@ -1980,8 +1986,11 @@ def testComb(self): for n, k in (True, True), (True, False), (False, False): self.assertEqual(comb(n, k), 1) self.assertIs(type(comb(n, k)), int) + self.assertEqual(comb(IntSubclass(5), IntSubclass(2)), 10) self.assertEqual(comb(MyIndexable(5), MyIndexable(2)), 10) - self.assertIs(type(comb(MyIndexable(5), MyIndexable(2))), int) + for k in range(3): + self.assertIs(type(comb(IntSubclass(5), IntSubclass(k))), int) + self.assertIs(type(comb(MyIndexable(5), MyIndexable(k))), int) def test_main():