Skip to content

Commit 70d21c7

Browse files
committed
gh-117999: fixed small nonnegative integer powers of complex numbers
Before, handling of numbers with special values in components (infinities, nans, signed zero) was invalid. Simple example: >>> z = complex(1, -0.0) >>> z*z (1-0j) >>> z**2 (1+0j) Now: >>> z**2 (1-0j)
1 parent a5fef80 commit 70d21c7

File tree

3 files changed

+34
-7
lines changed

3 files changed

+34
-7
lines changed

Lib/test/test_complex.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66

77
from random import random
88
from math import atan2, isnan, copysign
9+
from cmath import log, exp, isclose, isnan as cisnan
10+
from functools import reduce
11+
from itertools import combinations
912
import operator
1013

1114
INF = float("inf")
@@ -330,6 +333,31 @@ def test_pow_with_small_integer_exponents(self):
330333
self.assertEqual(str(float_pow), str(int_pow))
331334
self.assertEqual(str(complex_pow), str(int_pow))
332335

336+
# Check that complex numbers with special components
337+
# are correctly handled.
338+
values = [complex(*_) for _ in combinations([1, -1, 0.0, -0.0, 2,
339+
-3, INF, -INF, NAN], 2)]
340+
exponents = [0, 1, 2, 3, 4, 5, 6, 19]
341+
for z in values:
342+
for e in exponents:
343+
try:
344+
r_pow = z**e
345+
except OverflowError:
346+
continue
347+
r_pro = reduce(lambda x, y: x*y, [z]*e) if e else 1+0j
348+
test = str(r_pow) == str(r_pro)
349+
if not test:
350+
# We might fail here, because associativity of multiplication
351+
# is broken already for floats.
352+
# Consider z = 1-1j. Then z*z*z*z = ((z*z)*z)*z = -4+0j,
353+
# while in the algorithm for pow() a diffenent grouping
354+
# of operations is used: z**4 = (z*z)*(z*z) = -4-0j.
355+
r_pro = exp(e*log(z))
356+
self.assertTrue(test or isclose(r_pow, r_pro))
357+
if not cisnan(r_pow):
358+
self.assertEqual(copysign(1, r_pow.real), copysign(1, r_pro.real))
359+
self.assertEqual(copysign(1, r_pow.imag), copysign(1, r_pro.imag))
360+
333361
def test_boolcontext(self):
334362
for i in range(100):
335363
self.assertTrue(complex(random() + 1e-6, random() + 1e-6))
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fixed small nonnegative integer powers for complex numbers with special values
2+
(``±0.0``, infinities or nan's) in components. Patch by Sergey B Kirpichev.

Objects/complexobject.c

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -159,10 +159,8 @@ _Py_c_pow(Py_complex a, Py_complex b)
159159
static Py_complex
160160
c_powu(Py_complex x, long n)
161161
{
162-
Py_complex r, p;
162+
Py_complex p = x, r = n-- ? p : c_1;
163163
long mask = 1;
164-
r = c_1;
165-
p = x;
166164
while (mask > 0 && n >= mask) {
167165
if (n & mask)
168166
r = _Py_c_prod(r,p);
@@ -175,11 +173,10 @@ c_powu(Py_complex x, long n)
175173
static Py_complex
176174
c_powi(Py_complex x, long n)
177175
{
178-
if (n > 0)
179-
return c_powu(x,n);
180-
else
176+
if (n < 0)
181177
return _Py_c_quot(c_1, c_powu(x,-n));
182-
178+
else
179+
return c_powu(x,n);
183180
}
184181

185182
double

0 commit comments

Comments
 (0)