From 4b0564bc2d93fa93b48f5ecfb2cb8da35af90efc Mon Sep 17 00:00:00 2001 From: Mark Dickinson Date: Sun, 15 Aug 2021 10:27:31 +0100 Subject: [PATCH 1/8] Restore the old check for integer exponent --- Objects/complexobject.c | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/Objects/complexobject.c b/Objects/complexobject.c index 05cae32f528c467..06467a6b92d2620 100644 --- a/Objects/complexobject.c +++ b/Objects/complexobject.c @@ -174,12 +174,7 @@ c_powi(Py_complex x, long n) { Py_complex cn; - if (n > 100 || n < -100) { - cn.real = (double) n; - cn.imag = 0.; - return _Py_c_pow(x,cn); - } - else if (n > 0) + if (n > 0) return c_powu(x,n); else return _Py_c_quot(c_1, c_powu(x,-n)); @@ -514,6 +509,7 @@ static PyObject * complex_pow(PyObject *v, PyObject *w, PyObject *z) { Py_complex p; + Py_complex exponent; Py_complex a, b; TO_COMPLEX(v, a); TO_COMPLEX(w, b); @@ -523,20 +519,13 @@ complex_pow(PyObject *v, PyObject *w, PyObject *z) return NULL; } errno = 0; - // Check if w is an integer value that fits inside a C long, so we can - // use a faster algorithm. TO_COMPLEX(w, b), above, already handled the - // conversion from larger longs, as well as other types. - if (PyLong_Check(w)) { - int overflow = 0; - long int_exponent = PyLong_AsLongAndOverflow(w, &overflow); - if (int_exponent == -1 && PyErr_Occurred()) - return NULL; - if (overflow == 0) - p = c_powi(a, int_exponent); - else - p = _Py_c_pow(a, b); - } else { - p = _Py_c_pow(a, b); + exponent = b; + if (exponent.imag == 0.0 && exponent.real == rint(exponent.real) + && fabs(exponent.real) <= 100.0) { + p = c_powi(a, (long)exponent.real); + } + else { + p = _Py_c_pow(a, exponent); } Py_ADJUST_ERANGE2(p.real, p.imag); From 8895a6a357a987876daa19932b04c4076b7230b3 Mon Sep 17 00:00:00 2001 From: Mark Dickinson Date: Sun, 15 Aug 2021 10:30:41 +0100 Subject: [PATCH 2/8] Add regression test --- Lib/test/test_complex.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py index badb234bb303b60..c6b74559080a46b 100644 --- a/Lib/test/test_complex.py +++ b/Lib/test/test_complex.py @@ -269,6 +269,22 @@ def test_pow(self): except OverflowError: pass + # Check that z**2.0 is handled identically to z**2. + values = [ + complex(5.0, 12.0), + complex(5.0e100, 12.0e100), + complex(-4.0, INF), + complex(INF, 0.0), + ] + for value in values: + with self.subTest(value=value): + int_square = value**2 + float_square = value**2.0 + self.assertFloatsAreIdentical( + int_square.real, float_square.real) + self.assertFloatsAreIdentical( + int_square.imag, float_square.imag) + def test_boolcontext(self): for i in range(100): self.assertTrue(complex(random() + 1e-6, random() + 1e-6)) From 8f557fae29bef0dfff83141f38966244ca46ea66 Mon Sep 17 00:00:00 2001 From: Mark Dickinson Date: Sun, 15 Aug 2021 10:39:19 +0100 Subject: [PATCH 3/8] News entry --- .../Core and Builtins/2021-08-15-10-39-06.bpo-44698.lITKNc.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2021-08-15-10-39-06.bpo-44698.lITKNc.rst diff --git a/Misc/NEWS.d/next/Core and Builtins/2021-08-15-10-39-06.bpo-44698.lITKNc.rst b/Misc/NEWS.d/next/Core and Builtins/2021-08-15-10-39-06.bpo-44698.lITKNc.rst new file mode 100644 index 000000000000000..f197253e10ec695 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2021-08-15-10-39-06.bpo-44698.lITKNc.rst @@ -0,0 +1,2 @@ +Restore behaviour of complex exponentiation with integer-valued exponent of +type :class:`float` or :class:`complex`. From a925b1c86a718e9bb6cbb8b9f03e21875ba1f53e Mon Sep 17 00:00:00 2001 From: Mark Dickinson Date: Sun, 15 Aug 2021 11:03:02 +0100 Subject: [PATCH 4/8] Remove unused variable --- Objects/complexobject.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/Objects/complexobject.c b/Objects/complexobject.c index 06467a6b92d2620..9971e29f5f86e51 100644 --- a/Objects/complexobject.c +++ b/Objects/complexobject.c @@ -172,8 +172,6 @@ c_powu(Py_complex x, long n) static Py_complex c_powi(Py_complex x, long n) { - Py_complex cn; - if (n > 0) return c_powu(x,n); else From c03c9a5aaaa7b61868a0f833c195aae07191f548 Mon Sep 17 00:00:00 2001 From: Mark Dickinson Date: Sun, 15 Aug 2021 11:04:28 +0100 Subject: [PATCH 5/8] Use 'floor' rather than 'rint', just in case there are platforms that have floor but not rint --- Objects/complexobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/complexobject.c b/Objects/complexobject.c index 9971e29f5f86e51..3fa2633b0039c8e 100644 --- a/Objects/complexobject.c +++ b/Objects/complexobject.c @@ -518,7 +518,7 @@ complex_pow(PyObject *v, PyObject *w, PyObject *z) } errno = 0; exponent = b; - if (exponent.imag == 0.0 && exponent.real == rint(exponent.real) + if (exponent.imag == 0.0 && exponent.real == floor(exponent.real) && fabs(exponent.real) <= 100.0) { p = c_powi(a, (long)exponent.real); } From 6ac27856acb237db8d37aafbf103e6c443e3b53b Mon Sep 17 00:00:00 2001 From: Mark Dickinson Date: Sun, 15 Aug 2021 11:22:02 +0100 Subject: [PATCH 6/8] More comprehensive tests --- Lib/test/test_complex.py | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py index c6b74559080a46b..7ff109861564d34 100644 --- a/Lib/test/test_complex.py +++ b/Lib/test/test_complex.py @@ -269,21 +269,32 @@ def test_pow(self): except OverflowError: pass - # Check that z**2.0 is handled identically to z**2. + # Check that small integer exponents are handled identically + # regardless of type. values = [ complex(5.0, 12.0), complex(5.0e100, 12.0e100), complex(-4.0, INF), complex(INF, 0.0), ] + exponents = [-19, -5, -3, -2, -1, 0, 1, 2, 3, 5, 19] for value in values: - with self.subTest(value=value): - int_square = value**2 - float_square = value**2.0 - self.assertFloatsAreIdentical( - int_square.real, float_square.real) - self.assertFloatsAreIdentical( - int_square.imag, float_square.imag) + for exponent in exponents: + with self.subTest(value=value, exponent=exponent): + try: + int_pow = value**exponent + except OverflowError: + int_pow = "overflow" + try: + float_pow = value**float(exponent) + except OverflowError: + float_pow = "overflow" + try: + complex_pow = value**complex(exponent) + except OverflowError: + complex_pow = "overflow" + self.assertEqual(str(float_pow), str(int_pow)) + self.assertEqual(str(complex_pow), str(int_pow)) def test_boolcontext(self): for i in range(100): From b0babe9728c5f25198886df7ebffbd5b2f51d77d Mon Sep 17 00:00:00 2001 From: Mark Dickinson Date: Mon, 16 Aug 2021 18:48:38 +0100 Subject: [PATCH 7/8] Put new tests into their own method --- Lib/test/test_complex.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py index 7ff109861564d34..abd7e39cc5ae739 100644 --- a/Lib/test/test_complex.py +++ b/Lib/test/test_complex.py @@ -269,8 +269,9 @@ def test_pow(self): except OverflowError: pass + def test_pow_with_small_integer_exponents(self): # Check that small integer exponents are handled identically - # regardless of type. + # regardless of their type. values = [ complex(5.0, 12.0), complex(5.0e100, 12.0e100), From a74c34e10f5d29f33f7a2f38cc85b18901e5d32f Mon Sep 17 00:00:00 2001 From: Mark Dickinson Date: Mon, 16 Aug 2021 18:55:42 +0100 Subject: [PATCH 8/8] Remove unnecessary local variable; restore comment --- Objects/complexobject.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Objects/complexobject.c b/Objects/complexobject.c index 3fa2633b0039c8e..3e479497cfcc31f 100644 --- a/Objects/complexobject.c +++ b/Objects/complexobject.c @@ -507,7 +507,6 @@ static PyObject * complex_pow(PyObject *v, PyObject *w, PyObject *z) { Py_complex p; - Py_complex exponent; Py_complex a, b; TO_COMPLEX(v, a); TO_COMPLEX(w, b); @@ -517,13 +516,13 @@ complex_pow(PyObject *v, PyObject *w, PyObject *z) return NULL; } errno = 0; - exponent = b; - if (exponent.imag == 0.0 && exponent.real == floor(exponent.real) - && fabs(exponent.real) <= 100.0) { - p = c_powi(a, (long)exponent.real); + // Check whether the exponent has a small integer value, and if so use + // a faster and more accurate algorithm. + if (b.imag == 0.0 && b.real == floor(b.real) && fabs(b.real) <= 100.0) { + p = c_powi(a, (long)b.real); } else { - p = _Py_c_pow(a, exponent); + p = _Py_c_pow(a, b); } Py_ADJUST_ERANGE2(p.real, p.imag);