From 5b2f2bcadcf38f4254a71fcd7cf2a501257763f5 Mon Sep 17 00:00:00 2001 From: Mark Dickinson Date: Sun, 12 May 2019 18:54:08 +0100 Subject: [PATCH 1/9] Extend three-argument pow to allow the second argument to be negative. --- Doc/library/functions.rst | 13 +- Doc/whatsnew/3.8.rst | 7 + Lib/test/test_builtin.py | 1 - Lib/test/test_pow.py | 38 +++++ .../2019-05-12-18-46-50.bpo-36027.Q4YatQ.rst | 3 + Objects/longobject.c | 130 ++++++++++++++++-- 6 files changed, 176 insertions(+), 16 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2019-05-12-18-46-50.bpo-36027.Q4YatQ.rst diff --git a/Doc/library/functions.rst b/Doc/library/functions.rst index 613e4f74ac4176..31e9c62418a3a7 100644 --- a/Doc/library/functions.rst +++ b/Doc/library/functions.rst @@ -1218,9 +1218,16 @@ 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``. + + If *z* is present, all three of *x*, *y* and *z* must 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(ix, -y, z)`` is returned, + where *ix* is an inverse to *x* modulo *z*. + + .. versionchanged:: 3.8 + The three-argument form now allows the second argument to be negative, + permitting computation of modular inverses. .. function:: print(*objects, sep=' ', end='\\n', file=sys.stdout, flush=False) diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst index 58b23211cc67aa..e258fc6481fb77 100644 --- a/Doc/whatsnew/3.8.rst +++ b/Doc/whatsnew/3.8.rst @@ -221,6 +221,13 @@ Other Language Changes and Windows use this to properly terminate scripts in interactive sessions. (Contributed by Google via Gregory P. Smith in :issue:`1054041`.) +* The three-argument form of the :func:`pow` function now permits the second + argument (the exponent) to be negative in the case where the first argument + is relatively prime to the modulus. It then computes a modular + inverse to the first argument if the exponent is ``-1``, and a suitable + power of that inverse for other negative exponents. + (Contributed by Mark Dickinson in :issue:`36027`.) + New Modules =========== diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index 5674ea89b1c408..40909429a3292c 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -1122,7 +1122,6 @@ 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) self.assertRaises(ValueError, pow, 1, 2, 0) self.assertRaises(TypeError, pow) diff --git a/Lib/test/test_pow.py b/Lib/test/test_pow.py index cac1ae5ea2d8bc..6e05204263d3cd 100644 --- a/Lib/test/test_pow.py +++ b/Lib/test/test_pow.py @@ -1,5 +1,8 @@ import unittest +from math import gcd + + class PowTest(unittest.TestCase): def powtest(self, type): @@ -119,5 +122,40 @@ 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 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) + + + + # Modulus zero should still raise, even in the case below where + # the base and the modulus are still relatively prime. + with self.assertRaises(ValueError): + pow(1, -1, 0) + + + + + if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/Core and Builtins/2019-05-12-18-46-50.bpo-36027.Q4YatQ.rst b/Misc/NEWS.d/next/Core and Builtins/2019-05-12-18-46-50.bpo-36027.Q4YatQ.rst new file mode 100644 index 00000000000000..866309cddc682f --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2019-05-12-18-46-50.bpo-36027.Q4YatQ.rst @@ -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. diff --git a/Objects/longobject.c b/Objects/longobject.c index 9fb1fb02c276bd..d1407a1255ee7a 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -4180,6 +4180,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 + 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) */ static PyObject * long_pow(PyObject *v, PyObject *w, PyObject *x) @@ -4213,20 +4305,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) { @@ -4261,6 +4347,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); + 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 From 0a45fb85c28c93392c63a8ecb9acc5a899303f87 Mon Sep 17 00:00:00 2001 From: Mark Dickinson Date: Sun, 12 May 2019 19:01:48 +0100 Subject: [PATCH 2/9] Remove excessive whitespace. --- Lib/test/test_pow.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Lib/test/test_pow.py b/Lib/test/test_pow.py index 6e05204263d3cd..0283370106d54d 100644 --- a/Lib/test/test_pow.py +++ b/Lib/test/test_pow.py @@ -146,16 +146,11 @@ def test_negative_exponent(self): with self.assertRaises(ValueError): pow(a, -1001, m) - - # Modulus zero should still raise, even in the case below where # the base and the modulus are still relatively prime. with self.assertRaises(ValueError): pow(1, -1, 0) - - - if __name__ == "__main__": unittest.main() From 5f372ad4edff264e248cfde74bd1d89a93140bfc Mon Sep 17 00:00:00 2001 From: Mark Dickinson Date: Sun, 12 May 2019 19:03:18 +0100 Subject: [PATCH 3/9] Tidy up imports. --- Lib/test/test_pow.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_pow.py b/Lib/test/test_pow.py index 0283370106d54d..556d1f8d23c018 100644 --- a/Lib/test/test_pow.py +++ b/Lib/test/test_pow.py @@ -1,7 +1,6 @@ +import math import unittest -from math import gcd - class PowTest(unittest.TestCase): @@ -126,7 +125,7 @@ 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 gcd(a, m) == 1: + 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) From 11dec1eaf8f91560b9d4efe6bdf8aaafc64b0f99 Mon Sep 17 00:00:00 2001 From: Mark Dickinson Date: Sun, 12 May 2019 19:04:41 +0100 Subject: [PATCH 4/9] Remove a redundant test. --- Lib/test/test_pow.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Lib/test/test_pow.py b/Lib/test/test_pow.py index 556d1f8d23c018..56db1ffe0052fb 100644 --- a/Lib/test/test_pow.py +++ b/Lib/test/test_pow.py @@ -145,11 +145,6 @@ def test_negative_exponent(self): with self.assertRaises(ValueError): pow(a, -1001, m) - # Modulus zero should still raise, even in the case below where - # the base and the modulus are still relatively prime. - with self.assertRaises(ValueError): - pow(1, -1, 0) - if __name__ == "__main__": unittest.main() From fca728e26b9cf72c29ea694b588517546ad051a1 Mon Sep 17 00:00:00 2001 From: Mark Dickinson Date: Sat, 1 Jun 2019 10:34:55 +0100 Subject: [PATCH 5/9] Clarify that the negative third argument only applies for int type operands. --- Doc/library/functions.rst | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Doc/library/functions.rst b/Doc/library/functions.rst index 659cd121ec5b30..342f1dbe703419 100644 --- a/Doc/library/functions.rst +++ b/Doc/library/functions.rst @@ -1261,14 +1261,15 @@ are always available. They are listed here in alphabetical order. converted to float and a float result is delivered. For example, ``10**2`` returns ``100``, but ``10**-2`` returns ``0.01``. - If *z* is present, all three of *x*, *y* and *z* must 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(ix, -y, z)`` is returned, - where *ix* is an inverse to *x* modulo *z*. + 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(ix, -y, + z)`` is returned, where *ix* is an inverse to *x* modulo *z*. .. versionchanged:: 3.8 - The three-argument form now allows the second argument to be negative, - permitting computation of modular inverses. + 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) From 0d70b1bcf8d5e8c64306ac86d32ceb0f777774f8 Mon Sep 17 00:00:00 2001 From: Mark Dickinson Date: Sat, 1 Jun 2019 11:03:40 +0100 Subject: [PATCH 6/9] Minor test updates: restore the test for 'pow(-1, -2, 3)' in test_builtin, remove accidental spacing change. --- Lib/test/test_builtin.py | 2 ++ Lib/test/test_pow.py | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index b37230c0d34a51..b536cec064878f 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -1195,6 +1195,8 @@ def test_pow(self): self.assertAlmostEqual(pow(-1, 0.5), 1j) self.assertAlmostEqual(pow(-1, 1/3), 0.5 + 0.8660254037844386j) + # 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) diff --git a/Lib/test/test_pow.py b/Lib/test/test_pow.py index 56db1ffe0052fb..660ff80bbf522f 100644 --- a/Lib/test/test_pow.py +++ b/Lib/test/test_pow.py @@ -1,7 +1,6 @@ import math import unittest - class PowTest(unittest.TestCase): def powtest(self, type): From 4ea0bf0ef157d7bb23cb68b8d96e282655bcf2e7 Mon Sep 17 00:00:00 2001 From: Mark Dickinson Date: Sat, 1 Jun 2019 12:00:25 +0100 Subject: [PATCH 7/9] Cleaner wording in what's new entry. --- Doc/whatsnew/3.8.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst index 045b5cea7805e1..36c1f7a958f1bd 100644 --- a/Doc/whatsnew/3.8.rst +++ b/Doc/whatsnew/3.8.rst @@ -299,10 +299,10 @@ Other Language Changes (Contributed by Victor Stinner in :issue:`37032`.) * For integers, the three-argument form of the :func:`pow` function now permits - the second argument (the exponent) to be negative in the case where the first - argument is relatively prime to the modulus. It then computes a modular - inverse to the first argument if the exponent is ``-1``, and a suitable - power of that inverse for other negative exponents. + the 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`.) From be89d23186550c3c85f4fb683451bc3e9605c617 Mon Sep 17 00:00:00 2001 From: Mark Dickinson Date: Sat, 1 Jun 2019 12:00:51 +0100 Subject: [PATCH 8/9] Fix typo in what's new entry --- Doc/whatsnew/3.8.rst | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst index 36c1f7a958f1bd..23edee605187b8 100644 --- a/Doc/whatsnew/3.8.rst +++ b/Doc/whatsnew/3.8.rst @@ -299,10 +299,9 @@ Other Language Changes (Contributed by Victor Stinner in :issue:`37032`.) * For integers, the three-argument form of the :func:`pow` function now permits - the 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. + 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`.) From 4aaec00f67e9cff82b6fa7fcf2aa102f843b7944 Mon Sep 17 00:00:00 2001 From: Mark Dickinson Date: Sun, 2 Jun 2019 10:02:44 +0100 Subject: [PATCH 9/9] DOC: add example of computing an inverse; use inv_x instead of ix. --- Doc/library/functions.rst | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Doc/library/functions.rst b/Doc/library/functions.rst index 342f1dbe703419..b19429544dec07 100644 --- a/Doc/library/functions.rst +++ b/Doc/library/functions.rst @@ -1263,8 +1263,15 @@ are always available. They are listed here in alphabetical order. 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(ix, -y, - z)`` is returned, where *ix* is an inverse to *x* modulo *z*. + 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