This is a follow up of the old discussion. It’s outcome was, in short, that problems with complex arithmetic in Python can be solved only with a new imaginary type.
I’ll remind those problems with the latest implementation (CPython 3.13) of complex arithmetic on few examples:
>>> 2-0j # (1)
(2+0j)
>>> z = complex(-0.0, 2)
>>> cmath.asinh(z)
(-1.3169578969248166+1.5707963267948966j)
>>> cmath.log(z + cmath.sqrt(1 + z*z)) # (2)
(1.3169578969248166+1.5707963267948966j)
>>> (cmath.inf+1j)*2 # (3)
(inf+nanj)
>>> -0.0+1j # (4)
1j
>>> cmath.atan(z)
(-1.5707963267948966+0.5493061443340549j)
>>> 1j*(cmath.log(1 - 1j*z) - cmath.log(1 + 1j*z))/2 # (5)
(1.5707963267948966+0.5493061443340549j)
>>> repr(complex(-0.0, 1)) # (6)
'(-0+1j)'
Examples (1)
and (4)
show, that complex numbers in the CPython doesn’t follow to the rectangular notation (e.g. Complex number), i.e. a+bj != complex(a, b)
in general. Examples (2)
, (3)
and (5)
show that we can’t use complex arithmetic say for implementation of mathematical functions without a special treatment for complex numbers with special components (zero, signed zero or infinities). Even if the function is defined by simple formula like 1j/z
or 2*z
as in example (3)
. (You can take look on the mpmath sources to see that it’s a recurring problem for real-world applications.) Finally, example (6)
has funny negative integer zero, that also breaks eval(repr)
round-trip for complex numbers.
There is a known solution for all above issues from numerical experts: “Generally, mixed-mode arithmetic combining real and complex variables should be performed directly, not by first coercing the real to complex, lest the sign of zero be rendered uninformative; the same goes for combinations of pure imaginary quantities with complex variables.” (c) Kahan, W: Branch cuts for complex elementary functions.
Recently my pr added support for mixed-mode arithmetic rules, combining real and complex variables as specified by C standards since C99. Most C compilers implementing C99+ Annex G have only these special rules. So, in the CPython 3.14 examples (1)
-(3)
work correctly.
Lets discuss if we can fix the rest. That will add support for a new builtin type, say imaginary
, and imaginary literals will create instances of this type (no real part!). The patch itself is not too big, here is a draft to play with: Imaginary type and IEC 60559-compatible complex arithmetic by skirpichev · Pull Request #1 · skirpichev/cpython · GitHub.
For some further advocacy of mixed-mode rules (and imaginary type in particular), see Rationale for C99 (Annex G), WG14 document N3215 and Augmenting a Programming Language with Complex Arithmetic. Both the GNU GSL and the GNU MPC have special routines to do arithmetic with operands of different types, including pure-imaginary.
Edit: alternative approach is using special constructor forms. Say, complex(real=x, pure=True)
(a copy of real line) and complex(imag=y, pure=True)
(for pure-imaginary numbers). Imaginary literals will be translated to the second form. Also, doing complex arithmetic, floats will be automatically coerced to the first form. The rest (i.e. arithmetic rules) is same as in the proposed version. But I suspect that this implementation will be more complicated internally.