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.