Skip to content

Commit 5e896c1

Browse files
authored
Merge pull request #249 from skirpichev/perm/225
Add perm()
2 parents e9b3fdf + 5e88872 commit 5e896c1

File tree

2 files changed

+96
-1
lines changed

2 files changed

+96
-1
lines changed

gmp.c

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1911,6 +1911,72 @@ gmp_comb(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
19111911
return NULL;
19121912
}
19131913

1914+
static PyObject *
1915+
gmp_perm(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
1916+
{
1917+
if (nargs > 2 || nargs < 1) {
1918+
PyErr_SetString(PyExc_TypeError, "one or two arguments required");
1919+
return NULL;
1920+
}
1921+
if (nargs == 1) {
1922+
return gmp_fac(self, args[0]);
1923+
}
1924+
1925+
MPZ_Object *x, *y, *res = MPZ_new(0);
1926+
1927+
if (!res) {
1928+
return NULL; /* LCOV_EXCL_LINE */
1929+
}
1930+
CHECK_OP_INT(x, args[0]);
1931+
CHECK_OP_INT(y, args[1]);
1932+
if (zz_isneg(&x->z) || zz_isneg(&y->z)) {
1933+
PyErr_SetString(PyExc_ValueError,
1934+
"perm() not defined for negative values");
1935+
goto err;
1936+
}
1937+
1938+
int64_t n, k;
1939+
1940+
if ((zz_to_i64(&x->z, &n) || n > ULONG_MAX)
1941+
|| (zz_to_i64(&y->z, &k) || k > ULONG_MAX))
1942+
{
1943+
PyErr_Format(PyExc_OverflowError,
1944+
"perm() arguments should not exceed %ld",
1945+
ULONG_MAX);
1946+
goto err;
1947+
}
1948+
Py_XDECREF(x);
1949+
Py_XDECREF(y);
1950+
if (k > n) {
1951+
return (PyObject *)res;
1952+
}
1953+
1954+
MPZ_Object *den = MPZ_new(0);
1955+
1956+
if (!den) {
1957+
/* LCOV_EXCL_START */
1958+
PyErr_NoMemory();
1959+
goto err;
1960+
/* LCOV_EXCL_STOP */
1961+
}
1962+
if (zz_fac((uint64_t)n, &res->z)
1963+
|| zz_fac((uint64_t)(n-k), &den->z)
1964+
|| zz_div(&res->z, &den->z, ZZ_RNDD, &res->z, NULL))
1965+
{
1966+
/* LCOV_EXCL_START */
1967+
Py_DECREF(den);
1968+
PyErr_NoMemory();
1969+
goto err;
1970+
/* LCOV_EXCL_STOP */
1971+
}
1972+
Py_DECREF(den);
1973+
return (PyObject *)res;
1974+
err:
1975+
end:
1976+
Py_DECREF(res);
1977+
return NULL;
1978+
}
1979+
19141980
static zz_rnd
19151981
get_round_mode(PyObject *rndstr)
19161982
{
@@ -2124,6 +2190,9 @@ static PyMethodDef gmp_functions[] = {
21242190
("comb($module, n, k, /)\n--\n\nNumber of ways to choose k"
21252191
" items from n items without repetition and order.\n\n"
21262192
"Also called the binomial coefficient.")},
2193+
{"perm", (PyCFunction)gmp_perm, METH_FASTCALL,
2194+
("perm($module, n, k=None, /)\n--\n\nNumber of ways to choose k"
2195+
" items from n items without repetition and withorder.")},
21272196
{"_mpmath_normalize", (PyCFunction)gmp__mpmath_normalize, METH_FASTCALL,
21282197
NULL},
21292198
{"_mpmath_create", (PyCFunction)gmp__mpmath_create, METH_FASTCALL, NULL},
@@ -2216,7 +2285,7 @@ gmp_exec(PyObject *m)
22162285
"numbers.Integral.register(gmp.mpz)\n"
22172286
"gmp.fac = gmp.factorial\n"
22182287
"gmp.__all__ = ['comb', 'factorial', 'gcd', 'isqrt',\n"
2219-
" 'lcm', 'mpz']\n"
2288+
" 'lcm', 'mpz', 'perm']\n"
22202289
"gmp.__version__ = imp.version('python-gmp')\n");
22212290

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

tests/test_functions.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
isqrt_rem,
1717
lcm,
1818
mpz,
19+
perm,
1920
)
2021
from hypothesis import example, given
2122
from hypothesis.strategies import booleans, integers, lists, sampled_from
@@ -59,6 +60,21 @@ def test_comb(x, y):
5960
assert comb(x, y) == r
6061

6162

63+
@given(integers(min_value=0, max_value=12345),
64+
integers(min_value=0, max_value=12345))
65+
def test_perm(x, y):
66+
mx = mpz(x)
67+
my = mpz(y)
68+
r = math.perm(x, y)
69+
assert perm(mx, my) == r
70+
assert perm(mx, y) == r
71+
assert perm(x, my) == r
72+
assert perm(x, y) == r
73+
rx = math.factorial(x)
74+
assert perm(mx) == rx
75+
assert perm(x) == rx
76+
77+
6278
@given(bigints(), bigints(), bigints())
6379
@example(1<<(67*2), 1<<65, 1)
6480
@example(123, 1<<70, 1)
@@ -187,6 +203,16 @@ def test_interfaces():
187203
comb(2**1000, 1)
188204
with pytest.raises(OverflowError):
189205
comb(1, 2**1000)
206+
with pytest.raises(TypeError):
207+
perm(1, 2, 3)
208+
with pytest.raises(ValueError, match="not defined for negative values"):
209+
perm(-1, 2)
210+
with pytest.raises(ValueError, match="not defined for negative values"):
211+
perm(2, -1)
212+
with pytest.raises(OverflowError):
213+
perm(2**1000, 1)
214+
with pytest.raises(OverflowError):
215+
perm(1, 2**1000)
190216
with pytest.raises(TypeError):
191217
_mpmath_create(1j)
192218
with pytest.raises(TypeError):

0 commit comments

Comments
 (0)