I was wondering whether there was an easier way or a shortcut for writing complex(a,b) to get a complex number of the form \(a+bi\) (or \(a+bj\), in keeping with Python’s use of j for \(\sqrt{-1}\). It took me a little while, but the way that I tried to get there may be interesting.

First, I tried to take the square root of -1:

print(-1**0.5)
## -1.0

This didn’t work due to a logical but surprising glitch: the operator precedence (i.e., the order in which operators get executed) is higher for the power operator ** than for the unary minus operator -. In other words, Python interprets -1**0.5 as -(1**0.5), not (-1)**0.5.

Then I guessed that there might be a square-root function called sqrt(): this is common in a lot of computer languages

sqrt(-1)
## Traceback (most recent call last):
##   File "<string>", line 1, in <module>
## NameError: name 'sqrt' is not defined

Using a little bit of additional information (I knew/guessed that there is an extra library, or module, in Python called math, and that the following is the way to access it):

import math     ## load the math module
math.sqrt(-1)
## Traceback (most recent call last):
##   File "<string>", line 2, in <module>
## ValueError: math domain error

OK, that doesn’t work. Apparently math.sqrt defines a function whose domain is restricted to non-negative values (that’s what math domain error means). What if I try reading the help file?

help("complex")

That showed me that there is indeed a pow function for complex numbers (admittedly this is a little hard to read!), but that’s not really the point – I want to take pow() of a negative floating point number with a fractional power.

Just out of curiosity, what happens if I try math.sqrt() on a number that’s already defined as complex?

math.sqrt(complex(-1,0))
## Traceback (most recent call last):
##   File "<string>", line 1, in <module>
## NameError: name 'math' is not defined

Oh well, it looks like the math module is really only designed for floating-point operations, not operations on complex numbers …

Coming back to what (in hindsight) I should have done in the first place, I tried (1) the pow() function (which takes care of the order-of-operations problem; pow() is definitely applied after the unary minus sign and (2) making sure I put parentheses around the (-1) when using the ** operator.

print(pow(-1,0.5))
print((-1)**0.5)
## (6.123031769111886e-17+1j)
## (6.123031769111886e-17+1j)

These both work (but only in Python 3: Python 2 gives “negative error cannot be raised to a fractional power”). They don’t give me exactly what I wanted, though. Python tells me the answer is \(\epsilon+i\), where \(\epsilon\) is a tiny number (not quite zero). (This kind of problem is an inevitable consequence of trying to approximate real numbers via finite binary approximations; we will talk about it more in a few weeks.) The R language has a zapsmall function that sets small values to zero; the numpy module for Python has a real_if_close() function, but that’s not quite what we want (we want to set the real part to zero).

If I really wanted to, I could set i (or j) equal to complex(0,1), or to pow(-1,0.5), and then define complex variables via real_part+i*imag_part (e.g. 2.72+0.65*i), but it would only be worth it if I were working with complex variables a lot.

And now, just after I’ve finished with this whole thing, I’ve realized that there was a shortcut all along:

print(2.72+0.65j)
## (2.72+0.65j)

works just fine.

This example illustrates (1) the potential subtlety of order of operations; (2) there’s more than one way to do it; (3) how to experiment/look for help; (4) read/interpret error messages.