Skip to content

Commit 8c30627

Browse files
committed
Skip integer algorithm if the power base has special components
1 parent 31dd91d commit 8c30627

File tree

2 files changed

+81
-6
lines changed

2 files changed

+81
-6
lines changed

Lib/test/test_complex.py

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import cmath
12
import unittest
23
import sys
34
from test import support
@@ -7,7 +8,9 @@
78

89
from random import random
910
from math import isnan, copysign
11+
from itertools import combinations_with_replacement
1012
import operator
13+
import _testcapi
1114

1215
INF = float("inf")
1316
NAN = float("nan")
@@ -412,11 +415,6 @@ def test_pow(self):
412415
except OverflowError:
413416
pass
414417

415-
# Check that complex numbers with special components
416-
# are correctly handled.
417-
self.assertComplexesAreIdentical(complex(1, +0.0)**2, complex(1, +0.0))
418-
self.assertComplexesAreIdentical(complex(1, -0.0)**2, complex(1, -0.0))
419-
420418
# gh-113841: possible undefined division by 0 in _Py_c_pow()
421419
x, y = 9j, 33j**3
422420
with self.assertRaises(OverflowError):
@@ -450,6 +448,36 @@ def test_pow_with_small_integer_exponents(self):
450448
self.assertEqual(str(float_pow), str(int_pow))
451449
self.assertEqual(str(complex_pow), str(int_pow))
452450

451+
452+
# Check that complex numbers with special components
453+
# are correctly handled.
454+
values = [complex(*_)
455+
for _ in combinations_with_replacement([1, -1, 0.0, 0, -0.0, 2,
456+
-3, INF, -INF, NAN], 2)]
457+
exponents = [0, 1, 2, 3, 4, 5, 6, 19]
458+
for z in values:
459+
for e in exponents:
460+
with self.subTest(value=z, exponent=e):
461+
if cmath.isfinite(z) and z.real and z.imag:
462+
continue
463+
try:
464+
r_pow = z**e
465+
except OverflowError:
466+
continue
467+
# Use the generic complex power algorithm.
468+
r_pro, r_pro_errno = _testcapi._py_c_pow(z, e)
469+
self.assertEqual(r_pro_errno, 0)
470+
if isnan(r_pow.real):
471+
self.assertTrue(isnan(r_pro.real))
472+
else:
473+
self.assertEqual(copysign(1, r_pow.real),
474+
copysign(1, r_pro.real))
475+
if isnan(r_pow.imag):
476+
self.assertTrue(isnan(r_pro.imag))
477+
else:
478+
self.assertEqual(copysign(1, r_pow.imag),
479+
copysign(1, r_pro.imag))
480+
453481
def test_boolcontext(self):
454482
for i in range(100):
455483
self.assertTrue(complex(random() + 1e-6, random() + 1e-6))

Objects/complexobject.c

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

2727
/* elementary operations on complex numbers */
2828

29+
static Py_complex c_1 = {1., 0.};
30+
2931
Py_complex
3032
_Py_c_sum(Py_complex a, Py_complex b)
3133
{
@@ -336,6 +338,38 @@ _Py_c_pow(Py_complex a, Py_complex b)
336338
return r;
337339
}
338340

341+
/* Switch to exponentiation by squaring if integer exponent less that this. */
342+
#define INT_EXP_CUTOFF 100
343+
344+
static Py_complex
345+
c_powu(Py_complex x, long n)
346+
{
347+
Py_complex r, p;
348+
long mask = 1;
349+
r = c_1;
350+
p = x;
351+
assert(0 <= n);
352+
assert(n <= C_EXP_CUTOFF);
353+
while (n >= mask) {
354+
assert(mask > 0);
355+
if (n & mask)
356+
r = _Py_c_prod(r,p);
357+
mask <<= 1;
358+
p = _Py_c_prod(p,p);
359+
}
360+
return r;
361+
}
362+
363+
static Py_complex
364+
c_powi(Py_complex x, long n)
365+
{
366+
if (n > 0)
367+
return c_powu(x,n);
368+
else
369+
return _Py_c_quot(c_1, c_powu(x,-n));
370+
371+
}
372+
339373
double
340374
_Py_c_abs(Py_complex z)
341375
{
@@ -705,7 +739,20 @@ complex_pow(PyObject *v, PyObject *w, PyObject *z)
705739
return NULL;
706740
}
707741
errno = 0;
708-
p = _Py_c_pow(a, b);
742+
// Check whether the exponent has a small integer value, and if so use
743+
// a faster and more accurate algorithm.
744+
// Fallback on the generic code if the base has special
745+
// components (zeros or infinities).
746+
if (b.imag == 0.0 && b.real == floor(b.real) && fabs(b.real) <= INT_EXP_CUTOFF
747+
&& isfinite(a.real) && a.real && isfinite(a.imag) && a.imag)
748+
{
749+
p = c_powi(a, (long)b.real);
750+
_Py_ADJUST_ERANGE2(p.real, p.imag);
751+
}
752+
else {
753+
p = _Py_c_pow(a, b);
754+
}
755+
709756
if (errno == EDOM) {
710757
PyErr_SetString(PyExc_ZeroDivisionError,
711758
"zero to a negative or complex power");

0 commit comments

Comments
 (0)