summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPablo Galindo <Pablogsal@gmail.com>2019-02-07 07:04:02 (GMT)
committerRaymond Hettinger <rhettinger@users.noreply.github.com>2019-02-07 07:04:02 (GMT)
commitbc098515864d0d1ffe8fb97ca1a0526c30fee45a (patch)
tree2a39dfe4d826700abc132c01db7a92f0de76a7b7
parente9bc4172d18db9c182d8e04dd7b033097a994c06 (diff)
downloadcpython-bc098515864d0d1ffe8fb97ca1a0526c30fee45a.zip
cpython-bc098515864d0d1ffe8fb97ca1a0526c30fee45a.tar.gz
cpython-bc098515864d0d1ffe8fb97ca1a0526c30fee45a.tar.bz2
bpo-35606: Implement math.prod (GH-11359)
-rw-r--r--Doc/library/math.rst12
-rw-r--r--Doc/whatsnew/3.8.rst9
-rw-r--r--Lib/test/test_math.py31
-rw-r--r--Misc/NEWS.d/next/Library/2018-12-29-21-59-03.bpo-35606.NjGjou.rst3
-rw-r--r--Modules/clinic/mathmodule.c.h39
-rw-r--r--Modules/mathmodule.c167
6 files changed, 260 insertions, 1 deletions
diff --git a/Doc/library/math.rst b/Doc/library/math.rst
index 76226c2..7129525 100644
--- a/Doc/library/math.rst
+++ b/Doc/library/math.rst
@@ -178,6 +178,18 @@ Number-theoretic and representation functions
of *x* and are floats.
+.. function:: prod(iterable, *, start=1)
+
+ Calculate the product of all the elements in the input *iterable*.
+ The default *start* value for the product is ``1``.
+
+ When the iterable is empty, return the start value. This function is
+ intended specifically for use with numeric values and may reject
+ non-numeric types.
+
+ .. versionadded:: 3.8
+
+
.. function:: remainder(x, y)
Return the IEEE 754-style remainder of *x* with respect to *y*. For
diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst
index a3982b0..a90bc27 100644
--- a/Doc/whatsnew/3.8.rst
+++ b/Doc/whatsnew/3.8.rst
@@ -171,6 +171,15 @@ json.tool
Add option ``--json-lines`` to parse every input line as separate JSON object.
(Contributed by Weipeng Hong in :issue:`31553`.)
+
+math
+----
+
+Added new function, :func:`math.prod`, as analogous function to :func:`sum`
+that returns the product of a 'start' value (default: 1) times an iterable of
+numbers. (Contributed by Pablo Galindo in :issue:`issue35606`)
+
+
os.path
-------
diff --git a/Lib/test/test_math.py b/Lib/test/test_math.py
index f9b11f3..083759ca 100644
--- a/Lib/test/test_math.py
+++ b/Lib/test/test_math.py
@@ -1724,6 +1724,37 @@ class IsCloseTests(unittest.TestCase):
self.assertAllClose(fraction_examples, rel_tol=1e-8)
self.assertAllNotClose(fraction_examples, rel_tol=1e-9)
+ def test_prod(self):
+ prod = math.prod
+ self.assertEqual(prod([]), 1)
+ self.assertEqual(prod([], start=5), 5)
+ self.assertEqual(prod(list(range(2,8))), 5040)
+ self.assertEqual(prod(iter(list(range(2,8)))), 5040)
+ self.assertEqual(prod(range(1, 10), start=10), 3628800)
+
+ self.assertEqual(prod([1, 2, 3, 4, 5]), 120)
+ self.assertEqual(prod([1.0, 2.0, 3.0, 4.0, 5.0]), 120.0)
+ self.assertEqual(prod([1, 2, 3, 4.0, 5.0]), 120.0)
+ self.assertEqual(prod([1.0, 2.0, 3.0, 4, 5]), 120.0)
+
+ # Test overflow in fast-path for integers
+ self.assertEqual(prod([1, 1, 2**32, 1, 1]), 2**32)
+ # Test overflow in fast-path for floats
+ self.assertEqual(prod([1.0, 1.0, 2**32, 1, 1]), float(2**32))
+
+ self.assertRaises(TypeError, prod)
+ self.assertRaises(TypeError, prod, 42)
+ self.assertRaises(TypeError, prod, ['a', 'b', 'c'])
+ self.assertRaises(TypeError, prod, ['a', 'b', 'c'], '')
+ self.assertRaises(TypeError, prod, [b'a', b'c'], b'')
+ values = [bytearray(b'a'), bytearray(b'b')]
+ self.assertRaises(TypeError, prod, values, bytearray(b''))
+ self.assertRaises(TypeError, prod, [[1], [2], [3]])
+ self.assertRaises(TypeError, prod, [{2:3}])
+ self.assertRaises(TypeError, prod, [{2:3}]*2, {2:3})
+ self.assertRaises(TypeError, prod, [[1], [2], [3]], [])
+ with self.assertRaises(TypeError):
+ prod([10, 20], [30, 40]) # start is a keyword-only argument
def test_main():
from doctest import DocFileSuite
diff --git a/Misc/NEWS.d/next/Library/2018-12-29-21-59-03.bpo-35606.NjGjou.rst b/Misc/NEWS.d/next/Library/2018-12-29-21-59-03.bpo-35606.NjGjou.rst
new file mode 100644
index 0000000..d70b0bc
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2018-12-29-21-59-03.bpo-35606.NjGjou.rst
@@ -0,0 +1,3 @@
+Implement :func:`math.prod` as analogous function to :func:`sum` that
+returns the product of a 'start' value (default: 1) times an iterable of
+numbers. Patch by Pablo Galindo.
diff --git a/Modules/clinic/mathmodule.c.h b/Modules/clinic/mathmodule.c.h
index 82a4c4a..b99a8de 100644
--- a/Modules/clinic/mathmodule.c.h
+++ b/Modules/clinic/mathmodule.c.h
@@ -556,4 +556,41 @@ math_isclose(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject
exit:
return return_value;
}
-/*[clinic end generated code: output=0664f30046da09fe input=a9049054013a1b77]*/
+
+PyDoc_STRVAR(math_prod__doc__,
+"prod($module, iterable, /, *, start=1)\n"
+"--\n"
+"\n"
+"Calculate the product of all the elements in the input iterable.\n"
+"\n"
+"The default start value for the product is 1.\n"
+"\n"
+"When the iterable is empty, return the start value. This function is\n"
+"intended specifically for use with numeric values and may reject\n"
+"non-numeric types.");
+
+#define MATH_PROD_METHODDEF \
+ {"prod", (PyCFunction)(void(*)(void))math_prod, METH_FASTCALL|METH_KEYWORDS, math_prod__doc__},
+
+static PyObject *
+math_prod_impl(PyObject *module, PyObject *iterable, PyObject *start);
+
+static PyObject *
+math_prod(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
+{
+ PyObject *return_value = NULL;
+ static const char * const _keywords[] = {"", "start", NULL};
+ static _PyArg_Parser _parser = {"O|$O:prod", _keywords, 0};
+ PyObject *iterable;
+ PyObject *start = NULL;
+
+ if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser,
+ &iterable, &start)) {
+ goto exit;
+ }
+ return_value = math_prod_impl(module, iterable, start);
+
+exit:
+ return return_value;
+}
+/*[clinic end generated code: output=20505690ca6fe402 input=a9049054013a1b77]*/
diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c
index 83dab12..d2f8d53 100644
--- a/Modules/mathmodule.c
+++ b/Modules/mathmodule.c
@@ -2494,6 +2494,172 @@ math_isclose_impl(PyObject *module, double a, double b, double rel_tol,
}
+/*[clinic input]
+math.prod
+
+ iterable: object
+ /
+ *
+ start: object(c_default="NULL") = 1
+
+Calculate the product of all the elements in the input iterable.
+
+The default start value for the product is 1.
+
+When the iterable is empty, return the start value. This function is
+intended specifically for use with numeric values and may reject
+non-numeric types.
+[clinic start generated code]*/
+
+static PyObject *
+math_prod_impl(PyObject *module, PyObject *iterable, PyObject *start)
+/*[clinic end generated code: output=36153bedac74a198 input=4c5ab0682782ed54]*/
+{
+ PyObject *result = start;
+ PyObject *temp, *item, *iter;
+
+ iter = PyObject_GetIter(iterable);
+ if (iter == NULL) {
+ return NULL;
+ }
+
+ if (result == NULL) {
+ result = PyLong_FromLong(1);
+ if (result == NULL) {
+ Py_DECREF(iter);
+ return NULL;
+ }
+ } else {
+ Py_INCREF(result);
+ }
+#ifndef SLOW_PROD
+ /* Fast paths for integers keeping temporary products in C.
+ * Assumes all inputs are the same type.
+ * If the assumption fails, default to use PyObjects instead.
+ */
+ if (PyLong_CheckExact(result)) {
+ int overflow;
+ long i_result = PyLong_AsLongAndOverflow(result, &overflow);
+ /* If this already overflowed, don't even enter the loop. */
+ if (overflow == 0) {
+ Py_DECREF(result);
+ result = NULL;
+ }
+ /* Loop over all the items in the iterable until we finish, we overflow
+ * or we found a non integer element */
+ while(result == NULL) {
+ item = PyIter_Next(iter);
+ if (item == NULL) {
+ Py_DECREF(iter);
+ if (PyErr_Occurred()) {
+ return NULL;
+ }
+ return PyLong_FromLong(i_result);
+ }
+ if (PyLong_CheckExact(item)) {
+ long b = PyLong_AsLongAndOverflow(item, &overflow);
+ long x = i_result * b;
+ /* Continue if there is no overflow */
+ if (overflow == 0
+ && x < INT_MAX && x > INT_MIN
+ && !(b != 0 && x / i_result != b)) {
+ i_result = x;
+ Py_DECREF(item);
+ continue;
+ }
+ }
+ /* Either overflowed or is not an int.
+ * Restore real objects and process normally */
+ result = PyLong_FromLong(i_result);
+ if (result == NULL) {
+ Py_DECREF(item);
+ Py_DECREF(iter);
+ return NULL;
+ }
+ temp = PyNumber_Multiply(result, item);
+ Py_DECREF(result);
+ Py_DECREF(item);
+ result = temp;
+ if (result == NULL) {
+ Py_DECREF(iter);
+ return NULL;
+ }
+ }
+ }
+
+ /* Fast paths for floats keeping temporary products in C.
+ * Assumes all inputs are the same type.
+ * If the assumption fails, default to use PyObjects instead.
+ */
+ if (PyFloat_CheckExact(result)) {
+ double f_result = PyFloat_AS_DOUBLE(result);
+ Py_DECREF(result);
+ result = NULL;
+ while(result == NULL) {
+ item = PyIter_Next(iter);
+ if (item == NULL) {
+ Py_DECREF(iter);
+ if (PyErr_Occurred()) {
+ return NULL;
+ }
+ return PyFloat_FromDouble(f_result);
+ }
+ if (PyFloat_CheckExact(item)) {
+ f_result *= PyFloat_AS_DOUBLE(item);
+ Py_DECREF(item);
+ continue;
+ }
+ if (PyLong_CheckExact(item)) {
+ long value;
+ int overflow;
+ value = PyLong_AsLongAndOverflow(item, &overflow);
+ if (!overflow) {
+ f_result *= (double)value;
+ Py_DECREF(item);
+ continue;
+ }
+ }
+ result = PyFloat_FromDouble(f_result);
+ if (result == NULL) {
+ Py_DECREF(item);
+ Py_DECREF(iter);
+ return NULL;
+ }
+ temp = PyNumber_Multiply(result, item);
+ Py_DECREF(result);
+ Py_DECREF(item);
+ result = temp;
+ if (result == NULL) {
+ Py_DECREF(iter);
+ return NULL;
+ }
+ }
+ }
+#endif
+ /* Consume rest of the iterable (if any) that could not be handled
+ * by specialized functions above.*/
+ for(;;) {
+ item = PyIter_Next(iter);
+ if (item == NULL) {
+ /* error, or end-of-sequence */
+ if (PyErr_Occurred()) {
+ Py_DECREF(result);
+ result = NULL;
+ }
+ break;
+ }
+ temp = PyNumber_Multiply(result, item);
+ Py_DECREF(result);
+ Py_DECREF(item);
+ result = temp;
+ if (result == NULL)
+ break;
+ }
+ Py_DECREF(iter);
+ return result;
+}
+
+
static PyMethodDef math_methods[] = {
{"acos", math_acos, METH_O, math_acos_doc},
{"acosh", math_acosh, METH_O, math_acosh_doc},
@@ -2541,6 +2707,7 @@ static PyMethodDef math_methods[] = {
{"tan", math_tan, METH_O, math_tan_doc},
{"tanh", math_tanh, METH_O, math_tanh_doc},
MATH_TRUNC_METHODDEF
+ MATH_PROD_METHODDEF
{NULL, NULL} /* sentinel */
};