bpo-36027: Extend three-argument pow to negative second argument (GH-13266)
This commit is contained in:
parent
5ae299ac78
commit
c52996785a
|
@ -1277,9 +1277,24 @@ are always available. They are listed here in alphabetical order.
|
||||||
operands, the result has the same type as the operands (after coercion)
|
operands, the result has the same type as the operands (after coercion)
|
||||||
unless the second argument is negative; in that case, all arguments are
|
unless the second argument is negative; in that case, all arguments are
|
||||||
converted to float and a float result is delivered. For example, ``10**2``
|
converted to float and a float result is delivered. For example, ``10**2``
|
||||||
returns ``100``, but ``10**-2`` returns ``0.01``. If the second argument is
|
returns ``100``, but ``10**-2`` returns ``0.01``.
|
||||||
negative, the third argument must be omitted. If *z* is present, *x* and *y*
|
|
||||||
must be of integer types, and *y* must be non-negative.
|
For :class:`int` operands *x* and *y*, if *z* is present, *z* must also be
|
||||||
|
of integer type and *z* must be nonzero. If *z* is present and *y* is
|
||||||
|
negative, *x* must be relatively prime to *z*. In that case, ``pow(inv_x,
|
||||||
|
-y, z)`` is returned, where *inv_x* is an inverse to *x* modulo *z*.
|
||||||
|
|
||||||
|
Here's an example of computing an inverse for ``38`` modulo ``97``::
|
||||||
|
|
||||||
|
>>> pow(38, -1, 97)
|
||||||
|
23
|
||||||
|
>>> 23 * 38 % 97 == 1
|
||||||
|
True
|
||||||
|
|
||||||
|
.. versionchanged:: 3.8
|
||||||
|
For :class:`int` operands, the three-argument form of ``pow`` now allows
|
||||||
|
the second argument to be negative, permitting computation of modular
|
||||||
|
inverses.
|
||||||
|
|
||||||
|
|
||||||
.. function:: print(*objects, sep=' ', end='\\n', file=sys.stdout, flush=False)
|
.. function:: print(*objects, sep=' ', end='\\n', file=sys.stdout, flush=False)
|
||||||
|
|
|
@ -304,6 +304,12 @@ Other Language Changes
|
||||||
* Added new ``replace()`` method to the code type (:class:`types.CodeType`).
|
* Added new ``replace()`` method to the code type (:class:`types.CodeType`).
|
||||||
(Contributed by Victor Stinner in :issue:`37032`.)
|
(Contributed by Victor Stinner in :issue:`37032`.)
|
||||||
|
|
||||||
|
* For integers, the three-argument form of the :func:`pow` function now permits
|
||||||
|
the exponent to be negative in the case where the base is relatively prime to
|
||||||
|
the modulus. It then computes a modular inverse to the base when the exponent
|
||||||
|
is ``-1``, and a suitable power of that inverse for other negative exponents.
|
||||||
|
(Contributed by Mark Dickinson in :issue:`36027`.)
|
||||||
|
|
||||||
|
|
||||||
New Modules
|
New Modules
|
||||||
===========
|
===========
|
||||||
|
|
|
@ -1195,7 +1195,8 @@ class BuiltinTest(unittest.TestCase):
|
||||||
self.assertAlmostEqual(pow(-1, 0.5), 1j)
|
self.assertAlmostEqual(pow(-1, 0.5), 1j)
|
||||||
self.assertAlmostEqual(pow(-1, 1/3), 0.5 + 0.8660254037844386j)
|
self.assertAlmostEqual(pow(-1, 1/3), 0.5 + 0.8660254037844386j)
|
||||||
|
|
||||||
self.assertRaises(ValueError, pow, -1, -2, 3)
|
# See test_pow for additional tests for three-argument pow.
|
||||||
|
self.assertEqual(pow(-1, -2, 3), 1)
|
||||||
self.assertRaises(ValueError, pow, 1, 2, 0)
|
self.assertRaises(ValueError, pow, 1, 2, 0)
|
||||||
|
|
||||||
self.assertRaises(TypeError, pow)
|
self.assertRaises(TypeError, pow)
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import math
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
class PowTest(unittest.TestCase):
|
class PowTest(unittest.TestCase):
|
||||||
|
@ -119,5 +120,30 @@ class PowTest(unittest.TestCase):
|
||||||
eq(pow(a, -fiveto), expected)
|
eq(pow(a, -fiveto), expected)
|
||||||
eq(expected, 1.0) # else we didn't push fiveto to evenness
|
eq(expected, 1.0) # else we didn't push fiveto to evenness
|
||||||
|
|
||||||
|
def test_negative_exponent(self):
|
||||||
|
for a in range(-50, 50):
|
||||||
|
for m in range(-50, 50):
|
||||||
|
with self.subTest(a=a, m=m):
|
||||||
|
if m != 0 and math.gcd(a, m) == 1:
|
||||||
|
# Exponent -1 should give an inverse, with the
|
||||||
|
# same sign as m.
|
||||||
|
inv = pow(a, -1, m)
|
||||||
|
self.assertEqual(inv, inv % m)
|
||||||
|
self.assertEqual((inv * a - 1) % m, 0)
|
||||||
|
|
||||||
|
# Larger exponents
|
||||||
|
self.assertEqual(pow(a, -2, m), pow(inv, 2, m))
|
||||||
|
self.assertEqual(pow(a, -3, m), pow(inv, 3, m))
|
||||||
|
self.assertEqual(pow(a, -1001, m), pow(inv, 1001, m))
|
||||||
|
|
||||||
|
else:
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
pow(a, -1, m)
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
pow(a, -2, m)
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
pow(a, -1001, m)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
Allow computation of modular inverses via three-argument ``pow``: the second
|
||||||
|
argument is now permitted to be negative in the case where the first and
|
||||||
|
third arguments are relatively prime.
|
|
@ -4174,6 +4174,98 @@ long_divmod(PyObject *a, PyObject *b)
|
||||||
return z;
|
return z;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Compute an inverse to a modulo n, or raise ValueError if a is not
|
||||||
|
invertible modulo n. Assumes n is positive. The inverse returned
|
||||||
|
is whatever falls out of the extended Euclidean algorithm: it may
|
||||||
|
be either positive or negative, but will be smaller than n in
|
||||||
|
absolute value.
|
||||||
|
|
||||||
|
Pure Python equivalent for long_invmod:
|
||||||
|
|
||||||
|
def invmod(a, n):
|
||||||
|
b, c = 1, 0
|
||||||
|
while n:
|
||||||
|
q, r = divmod(a, n)
|
||||||
|
a, b, c, n = n, c, b - q*c, r
|
||||||
|
|
||||||
|
# at this point a is the gcd of the original inputs
|
||||||
|
if a == 1:
|
||||||
|
return b
|
||||||
|
raise ValueError("Not invertible")
|
||||||
|
*/
|
||||||
|
|
||||||
|
static PyLongObject *
|
||||||
|
long_invmod(PyLongObject *a, PyLongObject *n)
|
||||||
|
{
|
||||||
|
PyLongObject *b, *c;
|
||||||
|
|
||||||
|
/* Should only ever be called for positive n */
|
||||||
|
assert(Py_SIZE(n) > 0);
|
||||||
|
|
||||||
|
b = (PyLongObject *)PyLong_FromLong(1L);
|
||||||
|
if (b == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
c = (PyLongObject *)PyLong_FromLong(0L);
|
||||||
|
if (c == NULL) {
|
||||||
|
Py_DECREF(b);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
Py_INCREF(a);
|
||||||
|
Py_INCREF(n);
|
||||||
|
|
||||||
|
/* references now owned: a, b, c, n */
|
||||||
|
while (Py_SIZE(n) != 0) {
|
||||||
|
PyLongObject *q, *r, *s, *t;
|
||||||
|
|
||||||
|
if (l_divmod(a, n, &q, &r) == -1) {
|
||||||
|
goto Error;
|
||||||
|
}
|
||||||
|
Py_DECREF(a);
|
||||||
|
a = n;
|
||||||
|
n = r;
|
||||||
|
t = (PyLongObject *)long_mul(q, c);
|
||||||
|
Py_DECREF(q);
|
||||||
|
if (t == NULL) {
|
||||||
|
goto Error;
|
||||||
|
}
|
||||||
|
s = (PyLongObject *)long_sub(b, t);
|
||||||
|
Py_DECREF(t);
|
||||||
|
if (s == NULL) {
|
||||||
|
goto Error;
|
||||||
|
}
|
||||||
|
Py_DECREF(b);
|
||||||
|
b = c;
|
||||||
|
c = s;
|
||||||
|
}
|
||||||
|
/* references now owned: a, b, c, n */
|
||||||
|
|
||||||
|
Py_DECREF(c);
|
||||||
|
Py_DECREF(n);
|
||||||
|
if (long_compare(a, _PyLong_One)) {
|
||||||
|
/* a != 1; we don't have an inverse. */
|
||||||
|
Py_DECREF(a);
|
||||||
|
Py_DECREF(b);
|
||||||
|
PyErr_SetString(PyExc_ValueError,
|
||||||
|
"base is not invertible for the given modulus");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
/* a == 1; b gives an inverse modulo n */
|
||||||
|
Py_DECREF(a);
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
Error:
|
||||||
|
Py_DECREF(a);
|
||||||
|
Py_DECREF(b);
|
||||||
|
Py_DECREF(c);
|
||||||
|
Py_DECREF(n);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* pow(v, w, x) */
|
/* pow(v, w, x) */
|
||||||
static PyObject *
|
static PyObject *
|
||||||
long_pow(PyObject *v, PyObject *w, PyObject *x)
|
long_pow(PyObject *v, PyObject *w, PyObject *x)
|
||||||
|
@ -4207,20 +4299,14 @@ long_pow(PyObject *v, PyObject *w, PyObject *x)
|
||||||
Py_RETURN_NOTIMPLEMENTED;
|
Py_RETURN_NOTIMPLEMENTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Py_SIZE(b) < 0) { /* if exponent is negative */
|
if (Py_SIZE(b) < 0 && c == NULL) {
|
||||||
if (c) {
|
/* if exponent is negative and there's no modulus:
|
||||||
PyErr_SetString(PyExc_ValueError, "pow() 2nd argument "
|
return a float. This works because we know
|
||||||
"cannot be negative when 3rd argument specified");
|
|
||||||
goto Error;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
/* else return a float. This works because we know
|
|
||||||
that this calls float_pow() which converts its
|
that this calls float_pow() which converts its
|
||||||
arguments to double. */
|
arguments to double. */
|
||||||
Py_DECREF(a);
|
Py_DECREF(a);
|
||||||
Py_DECREF(b);
|
Py_DECREF(b);
|
||||||
return PyFloat_Type.tp_as_number->nb_power(v, w, x);
|
return PyFloat_Type.tp_as_number->nb_power(v, w, x);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (c) {
|
if (c) {
|
||||||
|
@ -4255,6 +4341,26 @@ long_pow(PyObject *v, PyObject *w, PyObject *x)
|
||||||
goto Done;
|
goto Done;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* if exponent is negative, negate the exponent and
|
||||||
|
replace the base with a modular inverse */
|
||||||
|
if (Py_SIZE(b) < 0) {
|
||||||
|
temp = (PyLongObject *)_PyLong_Copy(b);
|
||||||
|
if (temp == NULL)
|
||||||
|
goto Error;
|
||||||
|
Py_DECREF(b);
|
||||||
|
b = temp;
|
||||||
|
temp = NULL;
|
||||||
|
_PyLong_Negate(&b);
|
||||||
|
if (b == NULL)
|
||||||
|
goto Error;
|
||||||
|
|
||||||
|
temp = long_invmod(a, c);
|
||||||
|
if (temp == NULL)
|
||||||
|
goto Error;
|
||||||
|
Py_DECREF(a);
|
||||||
|
a = temp;
|
||||||
|
}
|
||||||
|
|
||||||
/* Reduce base by modulus in some cases:
|
/* Reduce base by modulus in some cases:
|
||||||
1. If base < 0. Forcing the base non-negative makes things easier.
|
1. If base < 0. Forcing the base non-negative makes things easier.
|
||||||
2. If base is obviously larger than the modulus. The "small
|
2. If base is obviously larger than the modulus. The "small
|
||||||
|
|
Loading…
Reference in New Issue