Skip to content

bpo-36027: Extend three-argument pow to negative second argument #13266

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jun 2, 2019
21 changes: 18 additions & 3 deletions Doc/library/functions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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)
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``
returns ``100``, but ``10**-2`` returns ``0.01``. If the second argument is
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.
returns ``100``, but ``10**-2`` returns ``0.01``.

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)
Expand Down
6 changes: 6 additions & 0 deletions Doc/whatsnew/3.8.rst
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,12 @@ Other Language Changes
* Added new ``replace()`` method to the code type (:class:`types.CodeType`).
(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
===========
Expand Down
3 changes: 2 additions & 1 deletion Lib/test/test_builtin.py
Original file line number Diff line number Diff line change
Expand Up @@ -1195,7 +1195,8 @@ def test_pow(self):
self.assertAlmostEqual(pow(-1, 0.5), 1j)
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(TypeError, pow)
Expand Down
26 changes: 26 additions & 0 deletions Lib/test/test_pow.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import math
import unittest

class PowTest(unittest.TestCase):
Expand Down Expand Up @@ -119,5 +120,30 @@ def test_bug705231(self):
eq(pow(a, -fiveto), expected)
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__":
unittest.main()
Original file line number Diff line number Diff line change
@@ -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.
130 changes: 118 additions & 12 deletions Objects/longobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -4174,6 +4174,98 @@ long_divmod(PyObject *a, PyObject *b)
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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh my, the "it may be either positive or negative" is alarming!
When I first read this, I feared you might have used the result of this utility function directly, making pow(5, -1, 8) return -3.
I see you didn't, but maybe a word of reassurance here would be helpful!

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;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like you could skip the final round of arithmetic (the long_mul and long_sub) used to update c, by leaving the loop early at this point if the remainder is 0, right?
Just make sure to update b on the way out.

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) */
static PyObject *
long_pow(PyObject *v, PyObject *w, PyObject *x)
Expand Down Expand Up @@ -4207,20 +4299,14 @@ long_pow(PyObject *v, PyObject *w, PyObject *x)
Py_RETURN_NOTIMPLEMENTED;
}

if (Py_SIZE(b) < 0) { /* if exponent is negative */
if (c) {
PyErr_SetString(PyExc_ValueError, "pow() 2nd argument "
"cannot be negative when 3rd argument specified");
goto Error;
}
else {
/* else return a float. This works because we know
if (Py_SIZE(b) < 0 && c == NULL) {
/* if exponent is negative and there's no modulus:
return a float. This works because we know
that this calls float_pow() which converts its
arguments to double. */
Py_DECREF(a);
Py_DECREF(b);
return PyFloat_Type.tp_as_number->nb_power(v, w, x);
}
Py_DECREF(a);
Py_DECREF(b);
return PyFloat_Type.tp_as_number->nb_power(v, w, x);
}

if (c) {
Expand Down Expand Up @@ -4255,6 +4341,26 @@ long_pow(PyObject *v, PyObject *w, PyObject *x)
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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think Py_SETREF() should be used in this block.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather keep it the way it is in this PR, for consistency with the existing previous block that negates the modulus. I'm happy to make a separate PR that updates both blocks (and other uses in longobject.c) to use Py_SETREF, but I don't want the long_pow code to be doing the same thing in two different ways in two places, and I want to keep this particular PR focused on the inversion feature.

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:
1. If base < 0. Forcing the base non-negative makes things easier.
2. If base is obviously larger than the modulus. The "small
Expand Down