Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
0651138
Add tests for math.binomial
KellerFuchs Dec 7, 2018
5665470
Update the spec for math.binomial to reject float inputs
KellerFuchs Dec 7, 2018
7a1d05b
Refactor testBinomial{Value,Type}Errors
KellerFuchs Jan 28, 2019
ff7084b
Rename math.binomial to math.combinations
KellerFuchs Jan 28, 2019
4a1d3bd
📜🤖 Added by blurb_it.
blurb-it[bot] Jan 2, 2019
bbf79c6
implemented math.comb function
FR4NKESTI3N Jan 2, 2019
87f71ac
Rename math.comb to math.combinations
KellerFuchs Jan 28, 2019
dba9385
Merge testsuites for math.combinations
KellerFuchs Jan 28, 2019
eb8860c
fixup! 📜🤖 Added by blurb_it.
KellerFuchs Jan 28, 2019
5169915
fixup! Rename math.comb to math.combinations
KellerFuchs Jan 28, 2019
4b5b1d3
lib/tests: math.combinations raises ValueError when k>n
KellerFuchs Jan 28, 2019
bd731e3
fixup! Add tests for math.binomial
KellerFuchs Jan 28, 2019
ecb234e
math.combinations: Improve notation in comments and exceptions
KellerFuchs Jan 28, 2019
a1e6496
fixup! lib/tests: math.combinations raises ValueError when k>n
KellerFuchs Feb 1, 2019
d1e4964
Rename math.combinations to math.comb
KellerFuchs Feb 1, 2019
2a5b01e
math.comb: Fixup mistake in exception message
KellerFuchs Feb 1, 2019
5b1532c
test_math: Rename testCombinations* to testComb*
KellerFuchs Feb 1, 2019
9c0a3d0
Changed name to comb in docs
FR4NKESTI3N Feb 2, 2019
9f50476
Update 2019-01-02-19-48-23.bpo-35431.FhG6QA.rst
FR4NKESTI3N Feb 2, 2019
bf49e8a
math.comb: Document the OverflowError behaviour
KellerFuchs Feb 3, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions Doc/library/math.rst
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,22 @@ Number-theoretic and representation functions
:meth:`x.__trunc__() <object.__trunc__>`.


.. function:: comb(n, k)

Return the binomial coefficient indexed by the pair of integers n >= k >= 0.

It is the coefficient of kth term in polynomial expansion of the expression
(1 + x)^n. It is also known as the number of ways to choose an unordered
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other parts of this file use double asterisks (**) for exponentiation, which is less likely to be confused with other meanings such as the XOR operator.

subset of k elements from a fixed set of n elements, usually called
*n choose k*.

Raises :exc:`TypeError` if argument(s) are non-integer, :exc:`ValueError`
if argument(s) are negative or k > n, and :exc:`OverflowError` if k and (n-k)
are too large (beyond `LLONG_MAX`).
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
are too large (beyond `LLONG_MAX`).
are too large; the max. value is implementation- and platform-dependent.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@FR4NKESTI3N Does that seem like reasonable wording?


.. 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
Expand Down
46 changes: 46 additions & 0 deletions Lib/test/test_math.py
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,52 @@ def testFactorialHugeInputs(self):
self.assertRaises(OverflowError, math.factorial, 10**100)
self.assertRaises(OverflowError, math.factorial, 1e100)

def testCombFactorial(self):
"""Test (n choose k) = n! / (k! (n-k)!) when 0 <= k <= n."""
for n in range(100):
for k in range(n+1):
self.assertEqual(math.comb(n, k),
math.factorial(n) // math.factorial(k) // math.factorial(n-k))

def testCombTriangle(self):
"""Test (n+1 choose k+1) = (n choose k) + (n choose k+1)"""
for n in range(100):
for k in range(n):
self.assertEqual(math.comb(n + 1, k + 1), math.comb(n, k) + math.comb(n, k + 1))

def testCombZero(self):
"""(n choose k) raises ValueError when k>n"""
for k in range(100):
for n in range(k):
self.assertRaises(ValueError, math.comb, n, k)

def testCombOne(self):
"""Test (n choose 0) = (n choose n) = 1"""
for n in range(100):
self.assertEqual(1, math.comb(n, 0))
self.assertEqual(1, math.comb(n, n))

def testCombValueErrors(self):
"""Test that math.comb raises ValueError on negative inputs or k>n."""
for neg in [-1, -10**100]:
self.assertRaises(ValueError, math.comb, 0, neg)
self.assertRaises(ValueError, math.comb, neg, 0)

for n in range(100):
for k in range(n+1, 100):
self.assertRaises(ValueError, math.comb, n, k)

def testCombOverflow(self):
"""math.comb raises OverflowError on inputs too large for C longs."""
# min(k, n - k) > LLONG_MAX)
self.assertRaises(OverflowError, math.comb, 10**400, 10**200)

def testCombTypeErrors(self):
"""Test math.comb raises TypeError on non-int inputs."""
for non_int in [-1e100, -1.0, math.pi, decimal.Decimal(5.2), "5"]:
self.assertRaises(TypeError, math.comb, 0, non_int)
self.assertRaises(TypeError, math.comb, non_int, 0)

def testFloor(self):
self.assertRaises(TypeError, math.floor)
self.assertEqual(int, type(math.floor(0.5)))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Implement :func:`math.comb` that returns binomial coefficient, that is the
coefficient of kth term in expansion of polynomial (1 + x)^n.
Patch by Keller Fuchs and Yash Aggarwal.
40 changes: 39 additions & 1 deletion Modules/clinic/mathmodule.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

146 changes: 146 additions & 0 deletions Modules/mathmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -2706,6 +2706,151 @@ math_prod_impl(PyObject *module, PyObject *iterable, PyObject *start)
}


/*[clinic input]
math.comb

n: object

k: object
/

Return the binomial coefficient indexed by the pair of integers n >= k >= 0.

It is the coefficient of kth term in polynomial expansion of the expression
(1 + x)^n. It is also known as the number of ways to choose an unordered
subset of k elements from a fixed set of n elements, usually called
*n choose k*.

Raises a TypeError if argument(s) are non-integer and ValueError
if argument(s) are negative or k > n.

[clinic start generated code]*/

static PyObject *
math_comb_impl(PyObject *module, PyObject *n, PyObject *k)
/*[clinic end generated code: output=bd2cec8d854f3493 input=75e1a19623bae7dc]*/
{
if (!(PyLong_Check(n) && PyLong_Check(k))) {
PyErr_SetString(PyExc_TypeError,
"comb() only accepts integer arguments");
return NULL;
}
n = PyNumber_Long(n);
if (n == NULL) {
return NULL;
}
k = PyNumber_Long(k);
if (k == NULL) {
Py_DECREF(n);
return NULL;
}

PyObject *val = NULL,
*temp_obj1 = NULL,
*temp_obj2 = NULL,
*dump_var = NULL;
int overflow, cmp;
long long i, terms;

cmp = PyObject_RichCompareBool(n, k, Py_LT);
if (cmp == 1) {
PyErr_Format(PyExc_ValueError,
"n must be an integer greater or equal to k");
goto fail_comb;
}
else if (cmp == -1) {
goto fail_comb;
}

/* b = min(b, a - b) */
dump_var = PyNumber_Subtract(n, k);
if (dump_var == NULL) {
goto fail_comb;
}
cmp = PyObject_RichCompareBool(k, dump_var, Py_GT);
if (cmp == 1) {
Py_DECREF(k);
k = dump_var;
dump_var = NULL;
}
else if (cmp == -1) {
goto fail_comb;
}
else {
Py_DECREF(dump_var);
dump_var = NULL;
}

terms = PyLong_AsLongLongAndOverflow(k, &overflow);
if (terms == -1 && PyErr_Occurred()) {
goto fail_comb;
}
else if (overflow == 1) {
PyErr_Format(PyExc_OverflowError,
"Either (n - k) or k must not exceed %lld",
LLONG_MAX);
goto fail_comb;
}
else if (overflow == -1 || terms < 0) {
PyErr_Format(PyExc_ValueError,
"k must be a positive integer");
goto fail_comb;
}

if (terms == 0) {
Py_DECREF(n);
Py_DECREF(k);
return PyNumber_Long(_PyLong_One);
}

val = PyNumber_Long(n);
for (i = 1; i < terms; ++i) {
temp_obj1 = PyLong_FromSsize_t(i);
if (temp_obj1 == NULL) {
goto fail_comb;
}
temp_obj2 = PyNumber_Subtract(n, temp_obj1);
if (temp_obj2 == NULL) {
goto fail_comb;
}
dump_var = val;
val = PyNumber_Multiply(val, temp_obj2);
if (val == NULL) {
goto fail_comb;
}
Py_DECREF(dump_var);
dump_var = NULL;
Py_DECREF(temp_obj2);
temp_obj2 = PyLong_FromUnsignedLongLong((unsigned long long)(i + 1));
if (temp_obj2 == NULL) {
goto fail_comb;
}
dump_var = val;
val = PyNumber_FloorDivide(val, temp_obj2);
if (val == NULL) {
goto fail_comb;
}
Py_DECREF(dump_var);
Py_DECREF(temp_obj1);
Py_DECREF(temp_obj2);
}
Py_DECREF(n);
Py_DECREF(k);

return val;

fail_comb:
Py_XDECREF(n);
Py_XDECREF(k);
Py_XDECREF(val);
Py_XDECREF(dump_var);
Py_XDECREF(temp_obj1);
Py_XDECREF(temp_obj2);

return NULL;
}


static PyMethodDef math_methods[] = {
{"acos", math_acos, METH_O, math_acos_doc},
{"acosh", math_acosh, METH_O, math_acosh_doc},
Expand Down Expand Up @@ -2754,6 +2899,7 @@ static PyMethodDef math_methods[] = {
{"tanh", math_tanh, METH_O, math_tanh_doc},
MATH_TRUNC_METHODDEF
MATH_PROD_METHODDEF
MATH_COMB_METHODDEF
{NULL, NULL} /* sentinel */
};

Expand Down