Skip to content

Commit 8b7c194

Browse files
authored
gh-120010: Fix invalid (nan+nanj) results in _Py_c_prod() (GH-120287)
In some cases, previously computed as (nan+nanj), we could recover meaningful component values in the result, see e.g. the C11, Annex G.5.1, routine _Cmultd(): >>> z = 1e300+1j >>> z*(nan+infj) # was (nan+nanj) (-inf+infj) That also fix some complex powers for small integer exponents, computed with optimized algorithm (by squaring): >>> z**5 # was (nan+nanj) Traceback (most recent call last): File "<python-input-1>", line 1, in <module> z**5 ~^^~ OverflowError: complex exponentiation
1 parent e991ac8 commit 8b7c194

File tree

3 files changed

+75
-4
lines changed

3 files changed

+75
-4
lines changed

Diff for: Lib/test/test_complex.py

+17
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,22 @@ def test_mul(self):
299299
self.assertRaises(TypeError, operator.mul, 1j, None)
300300
self.assertRaises(TypeError, operator.mul, None, 1j)
301301

302+
for z, w, r in [(1e300+1j, complex(INF, INF), complex(NAN, INF)),
303+
(1e300+1j, complex(NAN, INF), complex(-INF, INF)),
304+
(1e300+1j, complex(INF, NAN), complex(INF, INF)),
305+
(complex(INF, 1), complex(NAN, INF), complex(NAN, INF)),
306+
(complex(INF, 1), complex(INF, NAN), complex(INF, NAN)),
307+
(complex(NAN, 1), complex(1, INF), complex(-INF, NAN)),
308+
(complex(1, NAN), complex(1, INF), complex(NAN, INF)),
309+
(complex(1e200, NAN), complex(1e200, NAN), complex(INF, NAN)),
310+
(complex(1e200, NAN), complex(NAN, 1e200), complex(NAN, INF)),
311+
(complex(NAN, 1e200), complex(1e200, NAN), complex(NAN, INF)),
312+
(complex(NAN, 1e200), complex(NAN, 1e200), complex(-INF, NAN)),
313+
(complex(NAN, NAN), complex(NAN, NAN), complex(NAN, NAN))]:
314+
with self.subTest(z=z, w=w, r=r):
315+
self.assertComplexesAreIdentical(z * w, r)
316+
self.assertComplexesAreIdentical(w * z, r)
317+
302318
def test_mod(self):
303319
# % is no longer supported on complex numbers
304320
with self.assertRaises(TypeError):
@@ -340,6 +356,7 @@ def test_pow(self):
340356
self.assertAlmostEqual(pow(1j, 200), 1)
341357
self.assertRaises(ValueError, pow, 1+1j, 1+1j, 1+1j)
342358
self.assertRaises(OverflowError, pow, 1e200+1j, 1e200+1j)
359+
self.assertRaises(OverflowError, pow, 1e200+1j, 5)
343360
self.assertRaises(TypeError, pow, 1j, None)
344361
self.assertRaises(TypeError, pow, None, 1j)
345362
self.assertAlmostEqual(pow(1j, 0.5), 0.7071067811865476+0.7071067811865475j)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Correct invalid corner cases which resulted in ``(nan+nanj)`` output in complex
2+
multiplication, e.g., ``(1e300+1j)*(nan+infj)``. Patch by Sergey B Kirpichev.

Diff for: Objects/complexobject.c

+56-4
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,63 @@ _Py_c_neg(Py_complex a)
8585
}
8686

8787
Py_complex
88-
_Py_c_prod(Py_complex a, Py_complex b)
88+
_Py_c_prod(Py_complex z, Py_complex w)
8989
{
90-
Py_complex r;
91-
r.real = a.real*b.real - a.imag*b.imag;
92-
r.imag = a.real*b.imag + a.imag*b.real;
90+
double a = z.real, b = z.imag, c = w.real, d = w.imag;
91+
double ac = a*c, bd = b*d, ad = a*d, bc = b*c;
92+
Py_complex r = {ac - bd, ad + bc};
93+
94+
/* Recover infinities that computed as nan+nanj. See e.g. the C11,
95+
Annex G.5.1, routine _Cmultd(). */
96+
if (isnan(r.real) && isnan(r.imag)) {
97+
int recalc = 0;
98+
99+
if (isinf(a) || isinf(b)) { /* z is infinite */
100+
/* "Box" the infinity and change nans in the other factor to 0 */
101+
a = copysign(isinf(a) ? 1.0 : 0.0, a);
102+
b = copysign(isinf(b) ? 1.0 : 0.0, b);
103+
if (isnan(c)) {
104+
c = copysign(0.0, c);
105+
}
106+
if (isnan(d)) {
107+
d = copysign(0.0, d);
108+
}
109+
recalc = 1;
110+
}
111+
if (isinf(c) || isinf(d)) { /* w is infinite */
112+
/* "Box" the infinity and change nans in the other factor to 0 */
113+
c = copysign(isinf(c) ? 1.0 : 0.0, c);
114+
d = copysign(isinf(d) ? 1.0 : 0.0, d);
115+
if (isnan(a)) {
116+
a = copysign(0.0, a);
117+
}
118+
if (isnan(b)) {
119+
b = copysign(0.0, b);
120+
}
121+
recalc = 1;
122+
}
123+
if (!recalc && (isinf(ac) || isinf(bd) || isinf(ad) || isinf(bc))) {
124+
/* Recover infinities from overflow by changing nans to 0 */
125+
if (isnan(a)) {
126+
a = copysign(0.0, a);
127+
}
128+
if (isnan(b)) {
129+
b = copysign(0.0, b);
130+
}
131+
if (isnan(c)) {
132+
c = copysign(0.0, c);
133+
}
134+
if (isnan(d)) {
135+
d = copysign(0.0, d);
136+
}
137+
recalc = 1;
138+
}
139+
if (recalc) {
140+
r.real = Py_INFINITY*(a*c - b*d);
141+
r.imag = Py_INFINITY*(a*d + b*c);
142+
}
143+
}
144+
93145
return r;
94146
}
95147

0 commit comments

Comments
 (0)