Skip to content

Commit f2280be

Browse files
gh-117999: Fix small integer powers of complex numbers
* 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 4420cf4 commit f2280be

File tree

3 files changed

+116
-16
lines changed

3 files changed

+116
-16
lines changed

Lib/test/test_complex.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,35 @@ def test_pow_with_small_integer_exponents(self):
366366
self.assertEqual(str(float_pow), str(int_pow))
367367
self.assertEqual(str(complex_pow), str(int_pow))
368368

369+
# Check that complex numbers with special components
370+
# are correctly handled.
371+
for x in [2, -2, +0.0, -0.0, INF, -INF, NAN]:
372+
for y in [2, -2, +0.0, -0.0, INF, -INF, NAN]:
373+
c = complex(x, y)
374+
with self.subTest(c):
375+
self.assertComplexesAreIdentical(c**0, complex(1, +0.0))
376+
self.assertComplexesAreIdentical(c**1, c)
377+
self.assertComplexesAreIdentical(c**2, c*c)
378+
self.assertComplexesAreIdentical(c**3, c*(c*c))
379+
self.assertComplexesAreIdentical(c**4, (c*c)*(c*c))
380+
self.assertComplexesAreIdentical(c**5, c*((c*c)*(c*c)))
381+
for x in [+2, -2]:
382+
for y in [+0.0, -0.0]:
383+
with self.subTest(complex(x, y)):
384+
self.assertComplexesAreIdentical(complex(x, y)**-1, complex(1/x, -y))
385+
with self.subTest(complex(y, x)):
386+
self.assertComplexesAreIdentical(complex(y, x)**-1, complex(y, -1/x))
387+
for x in [+INF, -INF]:
388+
for y in [+1, -1]:
389+
c = complex(x, y)
390+
with self.subTest(c):
391+
self.assertComplexesAreIdentical(c**-1, complex(1/x, -0.0*y))
392+
self.assertComplexesAreIdentical(c**-2, complex(0.0, -y/x))
393+
c = complex(y, x)
394+
with self.subTest(c):
395+
self.assertComplexesAreIdentical(c**-1, complex(+0.0*y, -1/x))
396+
self.assertComplexesAreIdentical(c**-2, complex(-0.0, -y/x))
397+
369398
def test_boolcontext(self):
370399
for i in range(100):
371400
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: 85 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ class complex "PyComplexObject *" "&PyComplex_Type"
2121

2222
#include "clinic/complexobject.c.h"
2323

24-
/* elementary operations on complex numbers */
25-
2624
static Py_complex c_1 = {1., 0.};
2725

26+
/* elementary operations on complex numbers */
27+
2828
Py_complex
2929
_Py_c_sum(Py_complex a, Py_complex b)
3030
{
@@ -143,6 +143,64 @@ _Py_c_quot(Py_complex a, Py_complex b)
143143

144144
return r;
145145
}
146+
Py_complex
147+
_Py_dc_quot(double a, Py_complex b)
148+
{
149+
/* Divide a real number by a complex number.
150+
* Same as _Py_c_quot(), but without imaginary part.
151+
*/
152+
Py_complex r; /* the result */
153+
const double abs_breal = b.real < 0 ? -b.real : b.real;
154+
const double abs_bimag = b.imag < 0 ? -b.imag : b.imag;
155+
156+
if (abs_breal >= abs_bimag) {
157+
/* divide tops and bottom by b.real */
158+
if (abs_breal == 0.0) {
159+
errno = EDOM;
160+
r.real = r.imag = 0.0;
161+
}
162+
else {
163+
const double ratio = b.imag / b.real;
164+
const double denom = b.real + b.imag * ratio;
165+
r.real = a / denom;
166+
r.imag = (- a * ratio) / denom;
167+
}
168+
}
169+
else if (abs_bimag >= abs_breal) {
170+
/* divide tops and bottom by b.imag */
171+
const double ratio = b.real / b.imag;
172+
const double denom = b.real * ratio + b.imag;
173+
assert(b.imag != 0.0);
174+
r.real = (a * ratio) / denom;
175+
r.imag = (-a) / denom;
176+
}
177+
else {
178+
/* At least one of b.real or b.imag is a NaN */
179+
r.real = r.imag = Py_NAN;
180+
}
181+
182+
/* Recover infinities and zeros that computed as nan+nanj. See e.g.
183+
the C11, Annex G.5.2, routine _Cdivd(). */
184+
if (isnan(r.real) && isnan(r.imag)) {
185+
if (isinf(a)
186+
&& isfinite(b.real) && isfinite(b.imag))
187+
{
188+
const double x = copysign(isinf(a) ? 1.0 : 0.0, a);
189+
r.real = Py_INFINITY * (x*b.real);
190+
r.imag = Py_INFINITY * (- x*b.imag);
191+
}
192+
else if ((isinf(abs_breal) || isinf(abs_bimag))
193+
&& isfinite(a))
194+
{
195+
const double x = copysign(isinf(b.real) ? 1.0 : 0.0, b.real);
196+
const double y = copysign(isinf(b.imag) ? 1.0 : 0.0, b.imag);
197+
r.real = 0.0 * (a*x);
198+
r.imag = 0.0 * (-a*y);
199+
}
200+
}
201+
202+
return r;
203+
}
146204
#ifdef _M_ARM64
147205
#pragma optimize("", on)
148206
#endif
@@ -174,23 +232,31 @@ _Py_c_pow(Py_complex a, Py_complex b)
174232
r.real = len*cos(phase);
175233
r.imag = len*sin(phase);
176234

177-
_Py_ADJUST_ERANGE2(r.real, r.imag);
235+
if (isfinite(a.real) && isfinite(a.imag)
236+
&& isfinite(b.real) && isfinite(b.imag))
237+
{
238+
_Py_ADJUST_ERANGE2(r.real, r.imag);
239+
}
178240
}
179241
return r;
180242
}
181243

182244
static Py_complex
183245
c_powu(Py_complex x, long n)
184246
{
185-
Py_complex r, p;
186-
long mask = 1;
187-
r = c_1;
188-
p = x;
189-
while (mask > 0 && n >= mask) {
190-
if (n & mask)
191-
r = _Py_c_prod(r,p);
192-
mask <<= 1;
193-
p = _Py_c_prod(p,p);
247+
assert(n > 0);
248+
while ((n & 1) == 0) {
249+
x = _Py_c_prod(x, x);
250+
n >>= 1;
251+
}
252+
Py_complex r = x;
253+
n >>= 1;
254+
while (n) {
255+
x = _Py_c_prod(x, x);
256+
if (n & 1) {
257+
r = _Py_c_prod(r, x);
258+
}
259+
n >>= 1;
194260
}
195261
return r;
196262
}
@@ -199,10 +265,11 @@ static Py_complex
199265
c_powi(Py_complex x, long n)
200266
{
201267
if (n > 0)
202-
return c_powu(x,n);
268+
return c_powu(x, n);
269+
else if (n < 0)
270+
return _Py_dc_quot(1.0, c_powu(x, -n));
203271
else
204-
return _Py_c_quot(c_1, c_powu(x,-n));
205-
272+
return c_1;
206273
}
207274

208275
double
@@ -569,7 +636,9 @@ complex_pow(PyObject *v, PyObject *w, PyObject *z)
569636
// a faster and more accurate algorithm.
570637
if (b.imag == 0.0 && b.real == floor(b.real) && fabs(b.real) <= 100.0) {
571638
p = c_powi(a, (long)b.real);
572-
_Py_ADJUST_ERANGE2(p.real, p.imag);
639+
if (isfinite(a.real) && isfinite(a.imag)) {
640+
_Py_ADJUST_ERANGE2(p.real, p.imag);
641+
}
573642
}
574643
else {
575644
p = _Py_c_pow(a, b);

0 commit comments

Comments
 (0)