[PYTHON MATRIX-SIG] Why I won't add complex numbers to the base language

Guido van Rossum guido@CNRI.Reston.VA.US
Thu, 14 Sep 1995 08:27:04 -0400

[This is the first of a series of small essays that treat several
issues that have been brought up in the matrix sig.  I'd like to make
an effort to at least separate the threads by subject, if not putting
some issues to rest altogether.]

It's been proposed to add complex numbers to the base language, and
when I muttered I didn't like that the response was "there's no
mathematical reason not to."  Let me rebutt.

1. Mathematically, there is at least one difference: complex numbers
can't be compared (e.g. z1 < z2 is undefined when either has a nonzero
imaginary party).  Python will have to define this operation anyhow,
since deep inside it requires that each object type defines comparison
-- but it leads me to believe that there should be plenty of
algorithms out there that wouldn't work if some of their parameters
were complex numbers.  (At least if the designer of the code didn't
think about the possibility of complex numbers, the correctness of the
code can't be guaranteed in general.)  As long as real and complex are
distinct types in Python, there is no fear that complex numbers will
be introduced into the computation accidentally (e.g. by taking the
sqrt() of a negative number or by a rounding error) -- they are passed
in or used explicitly.

2. The available of GNU or other pseudo-free packages for
transcendental functions on complex numbers is still a big restriction
for Python's portability.  Everything that is part of the *core*
language has to be portable to every new platform.  Since some of
these platforms are non Unix, portability of code written by a crowd
who assume gcc is available "everywhere" is questionable.  There's
another problem with GNU code that's only relevant for a certain group
but which I care about anyway: people want to be able to embed Python
(at least it's core) in commercial applications, and if there's a GNU
licence attached to any part of that core, this is a problem.  (Don't
get me wrong -- I don't mind having Python extensions that use GNU
stuff, as long as they can be taken out cleanly and easily by those
who can't use GNU stuff, for whatever reason.)

3. The implementation of complex numbers as the only floating-point
type in Python would add considerable complexity (no pun intended :-).
I take that this should mean that if x is a real number (e.g. 0.0) and
z is a complex number (e.g. sqrt(-1.0)), type(x) == type(z).  This
means that the representation in memory of Python complex number
objects either has to contain the imaginary part at all times, or it
has to contain a flag telling whether there's an imaginary part or
not.  Since there's no space in the standard object header for such a
flag (at least not without changes that would have repercussions at
many other places), it would have to be an additional byte (or more)
in the complex number object itself.  Say an object header plus malloc
overhead is 12 bytes and a double precision float is 8 bytes.  If we
choose to have an additional flag it gets rounded up to 4 bytes (these
are typical and optimistic numbers -- on some platforms your mileage
may vary).  So we have two choices: either add a flag, so numbers
without an imaginary part are 12 (header) + 8(real) + 4 (flag) == 24
bytes and numbers with one are 12 + 8 + 4 + 8(imaginary) == 32 bytes,
or all numbers are 12 + 8 + 8 == 28 bytes.  Since most Python programs
won't be using numbers with imaginary parts (Python being a general
programming language), we may want to choose the version with a flag
-- but this add at least one if statement to each C function operating
on complex numbers (at least two for binary operators).  Timewise, the
fastest solution would be to always have the imaginary part there --
this would also make the code considerably cleaner.

Conclusion: there's a big price to be paid by every Python user for
having complex numbers as the only floating point type.

Consider the alternative: write an extension module ("complex") that
defines arithmetic on complex numbers (there are already examples of
how to do this in Python; a C version is totally plausible) and
another extensions module ("cmath") that defines transcendental
functions on complex number.  Now the price is only paid by those who
use the feature.  I think this solution can still lead to very clean
code.  I would even sanction a coding style where one writes "from
math import *" to use the standard math functions, so it would require
only a one-line change to user the complex versions instead ("from
cmath import *").

--Guido van Rossum <guido@CNRI.Reston.VA.US>
URL: <http://www.python.org/~guido/>

MATRIX-SIG  - SIG on Matrix Math for Python

send messages to: matrix-sig@python.org
administrivia to: matrix-sig-request@python.org