Skip to content

Commit d56f7e2

Browse files
gh-117999: Fix small integer powers of complex numbers (GH-124243)
* Fix the sign of zero components in the result. E.g. complex(1,-0.0)**2 now evaluates to complex(1,-0.0) instead of complex(1,-0.0). * Fix negative small integer powers of infinite complex numbers. E.g. complex(inf)**-1 now evaluates to complex(0,-0.0) instead of complex(nan,nan). * Powers of infinite numbers no longer raise OverflowError. E.g. complex(inf)**1 now evaluates to complex(inf) and complex(inf)**0.5 now evaluates to complex(inf,nan).
1 parent 1059e80 commit d56f7e2

3 files changed

Lines changed: 93 additions & 17 deletions

File tree

Lib/test/test_complex.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ def assertClose(self, x, y, eps=1e-9):
8484
# check that relative difference < eps
8585
self.assertTrue(abs(x-y)/abs(y) < eps)
8686

87+
def assertSameSign(self, x, y):
88+
if copysign(1., x) != copysign(1., y):
89+
self.fail(f'{x!r} and {y!r} have different signs')
90+
8791
def check_div(self, x, y):
8892
"""Compute complex z=x*y, and check that z/x==y and z/y==x."""
8993
z = x * y
@@ -446,6 +450,63 @@ def test_pow_with_small_integer_exponents(self):
446450
self.assertEqual(str(float_pow), str(int_pow))
447451
self.assertEqual(str(complex_pow), str(int_pow))
448452

453+
# Check that complex numbers with special components
454+
# are correctly handled.
455+
values = [complex(x, y)
456+
for x in [5, -5, +0.0, -0.0, INF, -INF, NAN]
457+
for y in [12, -12, +0.0, -0.0, INF, -INF, NAN]]
458+
for c in values:
459+
with self.subTest(value=c):
460+
self.assertComplexesAreIdentical(c**0, complex(1, +0.0))
461+
self.assertComplexesAreIdentical(c**1, c)
462+
self.assertComplexesAreIdentical(c**2, c*c)
463+
self.assertComplexesAreIdentical(c**3, c*(c*c))
464+
self.assertComplexesAreIdentical(c**3, (c*c)*c)
465+
if not c:
466+
continue
467+
for n in range(1, 9):
468+
with self.subTest(exponent=-n):
469+
self.assertComplexesAreIdentical(c**-n, 1/(c**n))
470+
471+
# Special cases for complex division.
472+
for x in [+2, -2]:
473+
for y in [+0.0, -0.0]:
474+
c = complex(x, y)
475+
with self.subTest(value=c):
476+
self.assertComplexesAreIdentical(c**-1, complex(1/x, -y))
477+
c = complex(y, x)
478+
with self.subTest(value=c):
479+
self.assertComplexesAreIdentical(c**-1, complex(y, -1/x))
480+
for x in [+INF, -INF]:
481+
for y in [+1, -1]:
482+
c = complex(x, y)
483+
with self.subTest(value=c):
484+
self.assertComplexesAreIdentical(c**-1, complex(1/x, -0.0*y))
485+
self.assertComplexesAreIdentical(c**-2, complex(0.0, -y/x))
486+
c = complex(y, x)
487+
with self.subTest(value=c):
488+
self.assertComplexesAreIdentical(c**-1, complex(+0.0*y, -1/x))
489+
self.assertComplexesAreIdentical(c**-2, complex(-0.0, -y/x))
490+
491+
# Test that zeroes has the same sign as small non-zero values.
492+
eps = 1e-11
493+
pairs = [(complex(x, y), complex(x, copysign(0.0, y)))
494+
for x in [+1, -1] for y in [+eps, -eps]]
495+
pairs += [(complex(y, x), complex(copysign(0.0, y), x))
496+
for x in [+1, -1] for y in [+eps, -eps]]
497+
for c1, c2 in pairs:
498+
for n in exponents:
499+
with self.subTest(value=c1, exponent=n):
500+
r1 = c1**n
501+
r2 = c2**n
502+
self.assertClose(r1, r2)
503+
self.assertSameSign(r1.real, r2.real)
504+
self.assertSameSign(r1.imag, r2.imag)
505+
self.assertNotEqual(r1.real, 0.0)
506+
if n != 0:
507+
self.assertNotEqual(r1.imag, 0.0)
508+
self.assertTrue(r2.real == 0.0 or r2.imag == 0.0)
509+
449510
def test_boolcontext(self):
450511
for i in range(100):
451512
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+
Fix calculation of powers of complex numbers. Small integer powers now produce correct sign of zero components. Negative powers of infinite numbers now evaluate to zero instead of NaN.
2+
Powers of infinite numbers no longer raise OverflowError.

Objects/complexobject.c

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@ class complex "PyComplexObject *" "&PyComplex_Type"
2626

2727
/* elementary operations on complex numbers */
2828

29-
static Py_complex c_1 = {1., 0.};
30-
3129
Py_complex
3230
_Py_c_sum(Py_complex a, Py_complex b)
3331
{
@@ -333,23 +331,33 @@ _Py_c_pow(Py_complex a, Py_complex b)
333331
r.real = len*cos(phase);
334332
r.imag = len*sin(phase);
335333

336-
_Py_ADJUST_ERANGE2(r.real, r.imag);
334+
if (isfinite(a.real) && isfinite(a.imag)
335+
&& isfinite(b.real) && isfinite(b.imag))
336+
{
337+
_Py_ADJUST_ERANGE2(r.real, r.imag);
338+
}
337339
}
338340
return r;
339341
}
340342

343+
#define INT_EXP_CUTOFF 100
344+
341345
static Py_complex
342346
c_powu(Py_complex x, long n)
343347
{
344-
Py_complex r, p;
345-
long mask = 1;
346-
r = c_1;
347-
p = x;
348-
while (mask > 0 && n >= mask) {
349-
if (n & mask)
350-
r = _Py_c_prod(r,p);
351-
mask <<= 1;
352-
p = _Py_c_prod(p,p);
348+
assert(0 < n && n <= INT_EXP_CUTOFF);
349+
while ((n & 1) == 0) {
350+
x = _Py_c_prod(x, x);
351+
n >>= 1;
352+
}
353+
Py_complex r = x;
354+
n >>= 1;
355+
while (n) {
356+
x = _Py_c_prod(x, x);
357+
if (n & 1) {
358+
r = _Py_c_prod(r, x);
359+
}
360+
n >>= 1;
353361
}
354362
return r;
355363
}
@@ -358,10 +366,11 @@ static Py_complex
358366
c_powi(Py_complex x, long n)
359367
{
360368
if (n > 0)
361-
return c_powu(x,n);
369+
return c_powu(x, n);
370+
else if (n < 0)
371+
return _Py_rc_quot(1.0, c_powu(x, -n));
362372
else
363-
return _Py_c_quot(c_1, c_powu(x,-n));
364-
373+
return (Py_complex){1., 0.};
365374
}
366375

367376
double
@@ -751,9 +760,13 @@ complex_pow(PyObject *v, PyObject *w, PyObject *z)
751760
errno = 0;
752761
// Check whether the exponent has a small integer value, and if so use
753762
// a faster and more accurate algorithm.
754-
if (b.imag == 0.0 && b.real == floor(b.real) && fabs(b.real) <= 100.0) {
763+
if (b.imag == 0.0 && b.real == floor(b.real)
764+
&& fabs(b.real) <= INT_EXP_CUTOFF)
765+
{
755766
p = c_powi(a, (long)b.real);
756-
_Py_ADJUST_ERANGE2(p.real, p.imag);
767+
if (isfinite(a.real) && isfinite(a.imag)) {
768+
_Py_ADJUST_ERANGE2(p.real, p.imag);
769+
}
757770
}
758771
else {
759772
p = _Py_c_pow(a, b);

0 commit comments

Comments
 (0)