Skip to content

Commit 4db428d

Browse files
committed
from 120010: fix invalid (nan+nanj) results in _Py_c_prod()
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.2, 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 optimised 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 29cb2cc commit 4db428d

File tree

2 files changed

+96
-3
lines changed

2 files changed

+96
-3
lines changed

Lib/test/test_complex.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,43 @@ def test_mul(self):
348348
self.assertComplexesAreIdentical((1+0j) * float(1.0), complex(1, 0))
349349
self.assertComplexesAreIdentical(float(1.0) * (1+0j), complex(1, 0))
350350

351+
self.assertComplexesAreIdentical((1e300+1j) * complex(INF, INF),
352+
complex(NAN, INF))
353+
self.assertComplexesAreIdentical(complex(INF, INF) * (1e300+1j),
354+
complex(NAN, INF))
355+
self.assertComplexesAreIdentical((1e300+1j) * complex(NAN, INF),
356+
complex(-INF, INF))
357+
self.assertComplexesAreIdentical(complex(NAN, INF) * (1e300+1j),
358+
complex(-INF, INF))
359+
self.assertComplexesAreIdentical((1e300+1j) * complex(INF, NAN),
360+
complex(INF, INF))
361+
self.assertComplexesAreIdentical(complex(INF, NAN) * (1e300+1j),
362+
complex(INF, INF))
363+
self.assertComplexesAreIdentical(complex(INF, 1) * complex(NAN, INF),
364+
complex(NAN, INF))
365+
self.assertComplexesAreIdentical(complex(INF, 1) * complex(INF, NAN),
366+
complex(INF, NAN))
367+
self.assertComplexesAreIdentical(complex(NAN, INF) * complex(INF, 1),
368+
complex(NAN, INF))
369+
self.assertComplexesAreIdentical(complex(INF, NAN) * complex(INF, 1),
370+
complex(INF, NAN))
371+
self.assertComplexesAreIdentical(complex(NAN, 1) * complex(1, INF),
372+
complex(-INF, NAN))
373+
self.assertComplexesAreIdentical(complex(1, NAN) * complex(1, INF),
374+
complex(NAN, INF))
375+
376+
self.assertComplexesAreIdentical(complex(1e200, NAN) * complex(1e200, NAN),
377+
complex(INF, NAN))
378+
self.assertComplexesAreIdentical(complex(1e200, NAN) * complex(NAN, 1e200),
379+
complex(NAN, INF))
380+
self.assertComplexesAreIdentical(complex(NAN, 1e200) * complex(1e200, NAN),
381+
complex(NAN, INF))
382+
self.assertComplexesAreIdentical(complex(NAN, 1e200) * complex(NAN, 1e200),
383+
complex(-INF, NAN))
384+
385+
self.assertComplexesAreIdentical(complex(NAN, NAN) * complex(NAN, NAN),
386+
complex(NAN, NAN))
387+
351388
def test_mod(self):
352389
# % is no longer supported on complex numbers
353390
with self.assertRaises(TypeError):
@@ -389,6 +426,7 @@ def test_pow(self):
389426
self.assertAlmostEqual(pow(1j, 200), 1)
390427
self.assertRaises(ValueError, pow, 1+1j, 1+1j, 1+1j)
391428
self.assertRaises(OverflowError, pow, 1e200+1j, 1e200+1j)
429+
self.assertRaises(OverflowError, pow, 1e200+1j, 5)
392430
self.assertRaises(TypeError, pow, 1j, None)
393431
self.assertRaises(TypeError, pow, None, 1j)
394432
self.assertAlmostEqual(pow(1j, 0.5), 0.7071067811865476+0.7071067811865475j)

Objects/complexobject.c

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,66 @@ _Py_c_neg(Py_complex a)
5555
}
5656

5757
Py_complex
58-
_Py_c_prod(Py_complex a, Py_complex b)
58+
_Py_c_prod(Py_complex z, Py_complex w)
5959
{
6060
Py_complex r;
61-
r.real = a.real*b.real - a.imag*b.imag;
62-
r.imag = a.real*b.imag + a.imag*b.real;
61+
double a = z.real, b = z.imag, c = w.real, d = w.imag;
62+
double ac = a*c, bd = b*d, ad = a*d, bc = b*c;
63+
64+
r.real = ac - bd;
65+
r.imag = ad + bc;
66+
67+
/* Recover infinities that computed as nan+nanj. See e.g. the C11,
68+
Annex G.5.2, routine _Cmultd(). */
69+
if (isnan(r.real) && isnan(r.imag)) {
70+
int recalc = 0;
71+
72+
if (isinf(a) || isinf(b)) { /* z is infinite */
73+
/* "Box" the infinity and change nans in the other factor to 0 */
74+
a = copysign(isinf(a) ? 1.0 : 0.0, a);
75+
b = copysign(isinf(b) ? 1.0 : 0.0, b);
76+
if (isnan(c)) {
77+
c = copysign(0.0, c);
78+
}
79+
if (isnan(d)) {
80+
d = copysign(0.0, d);
81+
}
82+
recalc = 1;
83+
}
84+
if (isinf(c) || isinf(d)) { /* w is infinite */
85+
/* "Box" the infinity and change nans in the other factor to 0 */
86+
c = copysign(isinf(c) ? 1.0 : 0.0, c);
87+
d = copysign(isinf(d) ? 1.0 : 0.0, d);
88+
if (isnan(a)) {
89+
a = copysign(0.0, a);
90+
}
91+
if (isnan(b)) {
92+
b = copysign(0.0, b);
93+
}
94+
recalc = 1;
95+
}
96+
if (!recalc && (isinf(ac) || isinf(bd) || isinf(ad) || isinf(bc))) {
97+
/* Recover infinities from overflow by changing nans to 0 */
98+
if (isnan(a)) {
99+
a = copysign(0.0, a);
100+
}
101+
if (isnan(b)) {
102+
b = copysign(0.0, b);
103+
}
104+
if (isnan(c)) {
105+
c = copysign(0.0, c);
106+
}
107+
if (isnan(d)) {
108+
d = copysign(0.0, d);
109+
}
110+
recalc = 1;
111+
}
112+
if (recalc) {
113+
r.real = Py_INFINITY*(a*c - b*d);
114+
r.imag = Py_INFINITY*(a*d + b*c);
115+
}
116+
}
117+
63118
return r;
64119
}
65120

0 commit comments

Comments
 (0)