Skip to content

Commit e9b3fdf

Browse files
authored
Merge pull request #248 from skirpichev/lcm/223
Add lcm()
2 parents f39362a + 3bb9511 commit e9b3fdf

File tree

4 files changed

+82
-2
lines changed

4 files changed

+82
-2
lines changed

gmp.c

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1713,7 +1713,7 @@ gmp_gcdext(PyObject *Py_UNUSED(module), PyObject *const *args,
17131713
Py_XDECREF(x);
17141714
Py_XDECREF(y);
17151715
if (ret == ZZ_MEM) {
1716-
PyErr_NoMemory(); /* LCOV_EXCL_LINE */
1716+
return PyErr_NoMemory(); /* LCOV_EXCL_LINE */
17171717
}
17181718
PyObject *tup = PyTuple_Pack(3, g, s, t);
17191719

@@ -1730,6 +1730,37 @@ gmp_gcdext(PyObject *Py_UNUSED(module), PyObject *const *args,
17301730
return NULL;
17311731
}
17321732

1733+
static PyObject *
1734+
gmp_lcm(PyObject *Py_UNUSED(module), PyObject *const *args, Py_ssize_t nargs)
1735+
{
1736+
MPZ_Object *res = MPZ_new(0);
1737+
1738+
if (!res || zz_from_i64(1, &res->z)) {
1739+
return PyErr_NoMemory(); /* LCOV_EXCL_LINE */
1740+
}
1741+
for (Py_ssize_t i = 0; i < nargs; i++) {
1742+
MPZ_Object *arg;
1743+
1744+
CHECK_OP_INT(arg, args[i]);
1745+
if (zz_cmp_i32(&res->z, 0) == ZZ_EQ) {
1746+
Py_DECREF(arg);
1747+
continue;
1748+
}
1749+
if (zz_lcm(&res->z, &arg->z, &res->z)) {
1750+
/* LCOV_EXCL_START */
1751+
Py_DECREF(res);
1752+
Py_DECREF(arg);
1753+
return PyErr_NoMemory();
1754+
/* LCOV_EXCL_STOP */
1755+
}
1756+
Py_DECREF(arg);
1757+
}
1758+
return (PyObject *)res;
1759+
end:
1760+
Py_DECREF(res);
1761+
return NULL;
1762+
}
1763+
17331764
static PyObject *
17341765
gmp_isqrt(PyObject *Py_UNUSED(module), PyObject *arg)
17351766
{
@@ -2071,6 +2102,9 @@ static PyMethodDef gmp_functions[] = {
20712102
{"gcdext", (PyCFunction)gmp_gcdext, METH_FASTCALL,
20722103
("gcdext($module, x, y, /)\n--\n\n"
20732104
"Compute extended GCD.")},
2105+
{"lcm", (PyCFunction)gmp_lcm, METH_FASTCALL,
2106+
("lcm($module, /, *integers)\n--\n\n"
2107+
"Least Common Multiple.")},
20742108
{"isqrt", gmp_isqrt, METH_O,
20752109
("isqrt($module, n, /)\n--\n\n"
20762110
"Return the integer part of the square root of n.")},
@@ -2182,7 +2216,7 @@ gmp_exec(PyObject *m)
21822216
"numbers.Integral.register(gmp.mpz)\n"
21832217
"gmp.fac = gmp.factorial\n"
21842218
"gmp.__all__ = ['comb', 'factorial', 'gcd', 'isqrt',\n"
2185-
" 'mpz']\n"
2219+
" 'lcm', 'mpz']\n"
21862220
"gmp.__version__ = imp.version('python-gmp')\n");
21872221

21882222
PyObject *res = PyRun_String(str, Py_file_input, ns, ns);

tests/test_functions.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
gcdext,
1515
isqrt,
1616
isqrt_rem,
17+
lcm,
1718
mpz,
1819
)
1920
from hypothesis import example, given
@@ -77,6 +78,17 @@ def test_gcdext_binary(x, y, c):
7778
assert fm(x, y) == r
7879

7980

81+
@given(bigints(), bigints())
82+
def test_lcm_binary(x, y):
83+
mx = mpz(x)
84+
my = mpz(y)
85+
r = math.lcm(x, y)
86+
assert lcm(mx, my) == r
87+
assert lcm(mx, y) == r
88+
assert lcm(x, my) == r
89+
assert lcm(x, y) == r
90+
91+
8092
@given(lists(bigints(), max_size=6), bigints())
8193
@example([], 1)
8294
@example([2, 3, 4], 1)
@@ -90,6 +102,15 @@ def test_gcd_nary(xs, c):
90102
assert gcd(*xs) == r
91103

92104

105+
@given(lists(bigints(), max_size=6))
106+
@example([])
107+
def test_lcm_nary(xs):
108+
mxs = list(map(mpz, xs))
109+
r = math.lcm(*xs)
110+
assert lcm(*mxs) == r
111+
assert lcm(*xs) == r
112+
113+
93114
@given(booleans(), bigints(min_value=0), bigints(),
94115
integers(min_value=1, max_value=1<<30),
95116
sampled_from(["n", "f", "c", "u", "d"]))
@@ -138,6 +159,10 @@ def test_interfaces():
138159
gcdext(2, 1j)
139160
with pytest.raises(TypeError):
140161
gcdext(2j, 2)
162+
with pytest.raises(TypeError):
163+
lcm(1j)
164+
with pytest.raises(TypeError):
165+
lcm(1, 1j)
141166
with pytest.raises(TypeError):
142167
isqrt(1j)
143168
with pytest.raises(TypeError):

zz.c

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1951,6 +1951,26 @@ zz_inverse(const zz_t *u, const zz_t *v, zz_t *w)
19511951
return ZZ_VAL;
19521952
}
19531953

1954+
zz_err
1955+
zz_lcm(const zz_t *u, const zz_t *v, zz_t *w)
1956+
{
1957+
zz_t g;
1958+
1959+
if (zz_init(&g) || zz_gcd(u, v, &g)) {
1960+
/* LCOV_EXCL_START */
1961+
err:
1962+
zz_clear(&g);
1963+
return ZZ_MEM;
1964+
/* LCOV_EXCL_STOP */
1965+
}
1966+
if (zz_div(u, &g, ZZ_RNDD, w, NULL) || zz_mul(w, v, w)) {
1967+
goto err; /* LCOV_EXCL_LINE */
1968+
}
1969+
zz_clear(&g);
1970+
(void)zz_abs(w, w);
1971+
return ZZ_OK;
1972+
}
1973+
19541974
zz_err
19551975
zz_powm(const zz_t *u, const zz_t *v, const zz_t *w, zz_t *res)
19561976
{

zz.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ zz_err zz_pow(const zz_t *u, uint64_t v, zz_t *w);
122122
zz_err zz_gcd(const zz_t *u, const zz_t *v, zz_t *w);
123123
zz_err zz_gcdext(const zz_t *u, const zz_t *v, zz_t *g, zz_t *s, zz_t *t);
124124
zz_err zz_inverse(const zz_t *u, const zz_t *v, zz_t *w);
125+
zz_err zz_lcm(const zz_t *u, const zz_t *v, zz_t *w);
125126

126127
zz_err zz_powm(const zz_t *u, const zz_t *v, const zz_t *w, zz_t *res);
127128

0 commit comments

Comments
 (0)