[Python-checkins] bpo-39648: Expand math.gcd() and math.lcm() to handle multiple arguments. (GH-18604)

Serhiy Storchaka webhook-mailer at python.org
Sun Feb 23 06:21:43 EST 2020


https://github.com/python/cpython/commit/559e7f165ad03731e6bc2211c0e6d8d9c02fb549
commit: 559e7f165ad03731e6bc2211c0e6d8d9c02fb549
branch: master
author: Serhiy Storchaka <storchaka at gmail.com>
committer: GitHub <noreply at github.com>
date: 2020-02-23T11:21:29Z
summary:

bpo-39648: Expand math.gcd() and math.lcm() to handle multiple arguments. (GH-18604)

* bpo-39648: Expand math.gcd() and math.lcm() to handle multiple arguments.

* Simplify fast path.

* Difine lcm() without arguments returning 1.

* Apply suggestions from code review

Co-Authored-By: Mark Dickinson <dickinsm at gmail.com>

Co-authored-by: Mark Dickinson <dickinsm at gmail.com>

files:
A Misc/NEWS.d/next/Library/2020-02-22-12-49-04.bpo-39648.Y-9N7F.rst
M Doc/library/math.rst
M Doc/whatsnew/3.9.rst
M Lib/test/test_math.py
M Modules/clinic/mathmodule.c.h
M Modules/mathmodule.c

diff --git a/Doc/library/math.rst b/Doc/library/math.rst
index d8ac35256d1b4..6ec1feee35a6d 100644
--- a/Doc/library/math.rst
+++ b/Doc/library/math.rst
@@ -126,23 +126,19 @@ Number-theoretic and representation functions
    <https://code.activestate.com/recipes/393090/>`_\.
 
 
-.. function:: gcd(a, b)
+.. function:: gcd(*integers)
 
-   Return the greatest common divisor of the integers *a* and *b*.  If either
-   *a* or *b* is nonzero, then the value of ``gcd(a, b)`` is the largest
-   positive integer that divides both *a* and *b*.  ``gcd(0, 0)`` returns
-   ``0``.
+   Return the greatest common divisor of the specified integer arguments.
+   If any of the arguments is nonzero, then the returned value is the largest
+   positive integer that is a divisor af all arguments.  If all arguments
+   are zero, then the returned value is ``0``.  ``gcd()`` without arguments
+   returns ``0``.
 
    .. versionadded:: 3.5
 
-
-.. function:: lcm(a, b)
-
-   Return the least common multiple of integers *a* and *b*.  The value of
-   ``lcm(a, b)`` is the smallest nonnegative integer that is a multiple of
-   both *a* and *b*.  If either *a* or *b* is zero then ``lcm(a, b)`` is zero.
-
-   .. versionadded:: 3.9
+   .. versionchanged:: 3.9
+      Added support for an arbitrary number of arguments. Formerly, only two
+      arguments were supported.
 
 
 .. function:: isclose(a, b, *, rel_tol=1e-09, abs_tol=0.0)
@@ -210,6 +206,17 @@ Number-theoretic and representation functions
    .. versionadded:: 3.8
 
 
+.. function:: lcm(*integers)
+
+   Return the least common multiple of the specified integer arguments.
+   If all arguments are nonzero, then the returned value is the smallest
+   positive integer that is a multiple of all arguments.  If any of the arguments
+   is zero, then the returned value is ``0``.  ``lcm()`` without arguments
+   returns ``1``.
+
+   .. versionadded:: 3.9
+
+
 .. function:: ldexp(x, i)
 
    Return ``x * (2**i)``.  This is essentially the inverse of function
diff --git a/Doc/whatsnew/3.9.rst b/Doc/whatsnew/3.9.rst
index 161675d32f670..a0ae4eb9b825a 100644
--- a/Doc/whatsnew/3.9.rst
+++ b/Doc/whatsnew/3.9.rst
@@ -216,8 +216,13 @@ import attempts.
 math
 ----
 
-Add :func:`math.lcm`: return the least common multiple of *a* and *b*.
-(Contributed by Ananthakrishnan in :issue:`39479`.)
+Expanded the :func:`math.gcd` function to handle multiple arguments.
+Formerly, it only supported two arguments.
+(Contributed by Serhiy Storchaka in :issue:`39648`.)
+
+Add :func:`math.lcm`: return the least common multiple of specified arguments.
+(Contributed by Mark Dickinson, Ananthakrishnan and Serhiy Storchaka in
+:issue:`39479` and :issue:`39648`.)
 
 Add :func:`math.nextafter`: return the next floating-point value after *x*
 towards *y*.
diff --git a/Lib/test/test_math.py b/Lib/test/test_math.py
index ad8273d50cc39..cc39402b3c60b 100644
--- a/Lib/test/test_math.py
+++ b/Lib/test/test_math.py
@@ -705,33 +705,32 @@ def testGcd(self):
         self.assertEqual(gcd(84, -120), 12)
         self.assertEqual(gcd(1216342683557601535506311712,
                              436522681849110124616458784), 32)
-        c = 652560
+
         x = 434610456570399902378880679233098819019853229470286994367836600566
         y = 1064502245825115327754847244914921553977
-        a = x * c
-        b = y * c
-        self.assertEqual(gcd(a, b), c)
-        self.assertEqual(gcd(b, a), c)
-        self.assertEqual(gcd(-a, b), c)
-        self.assertEqual(gcd(b, -a), c)
-        self.assertEqual(gcd(a, -b), c)
-        self.assertEqual(gcd(-b, a), c)
-        self.assertEqual(gcd(-a, -b), c)
-        self.assertEqual(gcd(-b, -a), c)
-        c = 576559230871654959816130551884856912003141446781646602790216406874
-        a = x * c
-        b = y * c
-        self.assertEqual(gcd(a, b), c)
-        self.assertEqual(gcd(b, a), c)
-        self.assertEqual(gcd(-a, b), c)
-        self.assertEqual(gcd(b, -a), c)
-        self.assertEqual(gcd(a, -b), c)
-        self.assertEqual(gcd(-b, a), c)
-        self.assertEqual(gcd(-a, -b), c)
-        self.assertEqual(gcd(-b, -a), c)
-
+        for c in (652560,
+                  576559230871654959816130551884856912003141446781646602790216406874):
+            a = x * c
+            b = y * c
+            self.assertEqual(gcd(a, b), c)
+            self.assertEqual(gcd(b, a), c)
+            self.assertEqual(gcd(-a, b), c)
+            self.assertEqual(gcd(b, -a), c)
+            self.assertEqual(gcd(a, -b), c)
+            self.assertEqual(gcd(-b, a), c)
+            self.assertEqual(gcd(-a, -b), c)
+            self.assertEqual(gcd(-b, -a), c)
+
+        self.assertEqual(gcd(), 0)
+        self.assertEqual(gcd(120), 120)
+        self.assertEqual(gcd(-120), 120)
+        self.assertEqual(gcd(120, 84, 102), 6)
+        self.assertEqual(gcd(120, 1, 84), 1)
+
+        self.assertRaises(TypeError, gcd, 120.0)
         self.assertRaises(TypeError, gcd, 120.0, 84)
         self.assertRaises(TypeError, gcd, 120, 84.0)
+        self.assertRaises(TypeError, gcd, 120, 1, 84.0)
         self.assertEqual(gcd(MyIndexable(120), MyIndexable(84)), 12)
 
     def testHypot(self):
@@ -989,9 +988,9 @@ def test_lcm(self):
         self.assertEqual(lcm(1216342683557601535506311712,
                              436522681849110124616458784),
                              16592536571065866494401400422922201534178938447014944)
+
         x = 43461045657039990237
         y = 10645022458251153277
-
         for c in (652560,
                   57655923087165495981):
             a = x * c
@@ -1005,9 +1004,18 @@ def test_lcm(self):
             self.assertEqual(lcm(-b, a), d)
             self.assertEqual(lcm(-a, -b), d)
             self.assertEqual(lcm(-b, -a), d)
-        self.assertEqual(lcm(MyIndexable(120), MyIndexable(84)), 840)
+
+        self.assertEqual(lcm(), 1)
+        self.assertEqual(lcm(120), 120)
+        self.assertEqual(lcm(-120), 120)
+        self.assertEqual(lcm(120, 84, 102), 14280)
+        self.assertEqual(lcm(120, 0, 84), 0)
+
+        self.assertRaises(TypeError, lcm, 120.0)
         self.assertRaises(TypeError, lcm, 120.0, 84)
         self.assertRaises(TypeError, lcm, 120, 84.0)
+        self.assertRaises(TypeError, lcm, 120, 0, 84.0)
+        self.assertEqual(lcm(MyIndexable(120), MyIndexable(84)), 840)
 
     def testLdexp(self):
         self.assertRaises(TypeError, math.ldexp)
diff --git a/Misc/NEWS.d/next/Library/2020-02-22-12-49-04.bpo-39648.Y-9N7F.rst b/Misc/NEWS.d/next/Library/2020-02-22-12-49-04.bpo-39648.Y-9N7F.rst
new file mode 100644
index 0000000000000..f205911ad9bfd
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2020-02-22-12-49-04.bpo-39648.Y-9N7F.rst
@@ -0,0 +1 @@
+Expanded :func:`math.gcd` and :func:`math.lcm` to handle multiple arguments.
diff --git a/Modules/clinic/mathmodule.c.h b/Modules/clinic/mathmodule.c.h
index df45a1a0c5e85..65f3dd4f520ae 100644
--- a/Modules/clinic/mathmodule.c.h
+++ b/Modules/clinic/mathmodule.c.h
@@ -2,36 +2,6 @@
 preserve
 [clinic start generated code]*/
 
-PyDoc_STRVAR(math_gcd__doc__,
-"gcd($module, x, y, /)\n"
-"--\n"
-"\n"
-"greatest common divisor of x and y");
-
-#define MATH_GCD_METHODDEF    \
-    {"gcd", (PyCFunction)(void(*)(void))math_gcd, METH_FASTCALL, math_gcd__doc__},
-
-static PyObject *
-math_gcd_impl(PyObject *module, PyObject *a, PyObject *b);
-
-static PyObject *
-math_gcd(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
-{
-    PyObject *return_value = NULL;
-    PyObject *a;
-    PyObject *b;
-
-    if (!_PyArg_CheckPositional("gcd", nargs, 2, 2)) {
-        goto exit;
-    }
-    a = args[0];
-    b = args[1];
-    return_value = math_gcd_impl(module, a, b);
-
-exit:
-    return return_value;
-}
-
 PyDoc_STRVAR(math_ceil__doc__,
 "ceil($module, x, /)\n"
 "--\n"
@@ -85,36 +55,6 @@ PyDoc_STRVAR(math_factorial__doc__,
 #define MATH_FACTORIAL_METHODDEF    \
     {"factorial", (PyCFunction)math_factorial, METH_O, math_factorial__doc__},
 
-PyDoc_STRVAR(math_lcm__doc__,
-"lcm($module, x, y, /)\n"
-"--\n"
-"\n"
-"least common multiple of x and y");
-
-#define MATH_LCM_METHODDEF    \
-    {"lcm", (PyCFunction)(void(*)(void))math_lcm, METH_FASTCALL, math_lcm__doc__},
-
-static PyObject *
-math_lcm_impl(PyObject *module, PyObject *a, PyObject *b);
-
-static PyObject *
-math_lcm(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
-{
-    PyObject *return_value = NULL;
-    PyObject *a;
-    PyObject *b;
-
-    if (!_PyArg_CheckPositional("lcm", nargs, 2, 2)) {
-        goto exit;
-    }
-    a = args[0];
-    b = args[1];
-    return_value = math_lcm_impl(module, a, b);
-
-exit:
-    return return_value;
-}
-
 PyDoc_STRVAR(math_trunc__doc__,
 "trunc($module, x, /)\n"
 "--\n"
@@ -925,4 +865,4 @@ math_ulp(PyObject *module, PyObject *arg)
 exit:
     return return_value;
 }
-/*[clinic end generated code: output=f8daa185c043a7b7 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=1eae2b3ef19568fa input=a9049054013a1b77]*/
diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c
index f74b7e1a34203..77e325cf0c63d 100644
--- a/Modules/mathmodule.c
+++ b/Modules/mathmodule.c
@@ -826,37 +826,125 @@ m_log10(double x)
 }
 
 
-/*[clinic input]
-math.gcd
+static PyObject *
+math_gcd(PyObject *module, PyObject * const *args, Py_ssize_t nargs)
+{
+    PyObject *res, *x;
+    Py_ssize_t i;
 
-    x as a: object
-    y as b: object
-    /
+    if (nargs == 0) {
+        return PyLong_FromLong(0);
+    }
+    res = PyNumber_Index(args[0]);
+    if (res == NULL) {
+        return NULL;
+    }
+    if (nargs == 1) {
+        Py_SETREF(res, PyNumber_Absolute(res));
+        return res;
+    }
+    for (i = 1; i < nargs; i++) {
+        x = PyNumber_Index(args[i]);
+        if (x == NULL) {
+            Py_DECREF(res);
+            return NULL;
+        }
+        if (res == _PyLong_One) {
+            /* Fast path: just check arguments.
+               It is okay to use identity comparison here. */
+            Py_DECREF(x);
+            continue;
+        }
+        Py_SETREF(res, _PyLong_GCD(res, x));
+        Py_DECREF(x);
+        if (res == NULL) {
+            return NULL;
+        }
+    }
+    return res;
+}
+
+PyDoc_STRVAR(math_gcd_doc,
+"gcd($module, *integers)\n"
+"--\n"
+"\n"
+"Greatest Common Divisor.");
 
-greatest common divisor of x and y
-[clinic start generated code]*/
 
 static PyObject *
-math_gcd_impl(PyObject *module, PyObject *a, PyObject *b)
-/*[clinic end generated code: output=7b2e0c151bd7a5d8 input=c2691e57fb2a98fa]*/
+long_lcm(PyObject *a, PyObject *b)
 {
-    PyObject *g;
+    PyObject *g, *m, *f, *ab;
 
-    a = PyNumber_Index(a);
-    if (a == NULL)
+    if (Py_SIZE(a) == 0 || Py_SIZE(b) == 0) {
+        return PyLong_FromLong(0);
+    }
+    g = _PyLong_GCD(a, b);
+    if (g == NULL) {
         return NULL;
-    b = PyNumber_Index(b);
-    if (b == NULL) {
-        Py_DECREF(a);
+    }
+    f = PyNumber_FloorDivide(a, g);
+    Py_DECREF(g);
+    if (f == NULL) {
         return NULL;
     }
-    g = _PyLong_GCD(a, b);
-    Py_DECREF(a);
-    Py_DECREF(b);
-    return g;
+    m = PyNumber_Multiply(f, b);
+    Py_DECREF(f);
+    if (m == NULL) {
+        return NULL;
+    }
+    ab = PyNumber_Absolute(m);
+    Py_DECREF(m);
+    return ab;
 }
 
 
+static PyObject *
+math_lcm(PyObject *module, PyObject * const *args, Py_ssize_t nargs)
+{
+    PyObject *res, *x;
+    Py_ssize_t i;
+
+    if (nargs == 0) {
+        return PyLong_FromLong(1);
+    }
+    res = PyNumber_Index(args[0]);
+    if (res == NULL) {
+        return NULL;
+    }
+    if (nargs == 1) {
+        Py_SETREF(res, PyNumber_Absolute(res));
+        return res;
+    }
+    for (i = 1; i < nargs; i++) {
+        x = PyNumber_Index(args[i]);
+        if (x == NULL) {
+            Py_DECREF(res);
+            return NULL;
+        }
+        if (res == _PyLong_Zero) {
+            /* Fast path: just check arguments.
+               It is okay to use identity comparison here. */
+            Py_DECREF(x);
+            continue;
+        }
+        Py_SETREF(res, long_lcm(res, x));
+        Py_DECREF(x);
+        if (res == NULL) {
+            return NULL;
+        }
+    }
+    return res;
+}
+
+
+PyDoc_STRVAR(math_lcm_doc,
+"lcm($module, *integers)\n"
+"--\n"
+"\n"
+"Least Common Multiple.");
+
+
 /* Call is_error when errno != 0, and where x is the result libm
  * returned.  is_error will usually set up an exception and return
  * true (1), but may return false (0) without setting up an exception.
@@ -2017,59 +2105,6 @@ math_factorial(PyObject *module, PyObject *arg)
 }
 
 
-/*[clinic input]
-math.lcm
-    x as a: object
-    y as b: object
-    /
-least common multiple of x and y
-[clinic start generated code]*/
-
-static PyObject *
-math_lcm_impl(PyObject *module, PyObject *a, PyObject *b)
-/*[clinic end generated code: output=6f83fb6d671074ba input=efb3d7b7334b7118]*/
-{
-    PyObject *g, *m, *f, *ab;
-
-    a = PyNumber_Index(a);
-    if (a == NULL) {
-        return NULL;
-    }
-    b = PyNumber_Index(b);
-    if (b == NULL) {
-        Py_DECREF(a);
-        return NULL;
-    }
-    if (_PyLong_Sign(a) == 0 || _PyLong_Sign(b) == 0) {
-        Py_DECREF(a);
-        Py_DECREF(b);
-        return PyLong_FromLong(0);
-    }
-    g = _PyLong_GCD(a, b);
-    if (g == NULL) {
-        Py_DECREF(a);
-        Py_DECREF(b);
-        return NULL;
-    }
-    f = PyNumber_FloorDivide(a, g);
-    Py_DECREF(g);
-    Py_DECREF(a);
-    if (f == NULL) {
-        Py_DECREF(b);
-        return NULL;
-    }
-    m = PyNumber_Multiply(f, b);
-    Py_DECREF(f);
-    Py_DECREF(b);
-    if (m == NULL) {
-        return NULL;
-    }
-    ab = PyNumber_Absolute(m);
-    Py_DECREF(m);
-    return ab;
-}
-
-
 /*[clinic input]
 math.trunc
 
@@ -3408,14 +3443,14 @@ static PyMethodDef math_methods[] = {
     MATH_FREXP_METHODDEF
     MATH_FSUM_METHODDEF
     {"gamma",           math_gamma,     METH_O,         math_gamma_doc},
-    MATH_GCD_METHODDEF
+    {"gcd",             (PyCFunction)(void(*)(void))math_gcd,       METH_FASTCALL,  math_gcd_doc},
     {"hypot",           (PyCFunction)(void(*)(void))math_hypot,     METH_FASTCALL,  math_hypot_doc},
     MATH_ISCLOSE_METHODDEF
     MATH_ISFINITE_METHODDEF
     MATH_ISINF_METHODDEF
     MATH_ISNAN_METHODDEF
     MATH_ISQRT_METHODDEF
-    MATH_LCM_METHODDEF
+    {"lcm",             (PyCFunction)(void(*)(void))math_lcm,       METH_FASTCALL,  math_lcm_doc},
     MATH_LDEXP_METHODDEF
     {"lgamma",          math_lgamma,    METH_O,         math_lgamma_doc},
     MATH_LOG_METHODDEF



More information about the Python-checkins mailing list