This post is intended for people with a little bit of programming experience and no prior mathematical background.
So let’s talk about numbers.
Numbers are curious things. On one hand, they represent one of the most natural things known to humans, which is quantity. It’s so natural to humans that even newborn babies are in tune with the difference between quantities of objects between 1 and 3, in that they notice when quantity changes much more vividly than other features like color or shape.
But our familiarity with quantity doesn’t change the fact that numbers themselves (as an idea) are a human invention. And they’re not like most human inventions, the kinds where you have to tinker with gears or circuits to get a machine that makes your cappuccino. No, these are mathematical inventions. These inventions exist only in our minds.
Numbers didn’t always exist. A long time ago, back when the Greeks philosophers were doing their philosophizing, negative numbers didn’t exist! In fact, it wasn’t until 1200 AD that the number zero was first considered in Europe. Zero, along with negative numbers and fractions and square roots and all the rest, were invented primarily to help people solve more problems than they could with the numbers they had available. That is, numbers were invented primarily as a way for people to describe their ideas in a useful way. People simply wondered “is there a number whose square gives you 2?” And after a while they just decided there was and called it
But with these new solutions came a host of new problems. You see, although I said mathematical inventions only exist in our minds, once they’re invented they gain a life of their own. You start to notice patterns in your mathematical objects and you have to figure out why they do the things they do. And numbers are a perfectly good example of this: once I notice that I can multiply a number by itself, I can ask how often these “perfect squares” occur. That is, what’s the pattern in the numbers
Other times, however, the things you invent turn out to make no sense at all, and you can prove they never existed in the first place! It’s an odd state of affairs, but we’re going to approach the subject of complex numbers from this mindset. We’re going to come up with a simple idea, the idea that negative numbers can be perfect squares, and explore the world of patterns it opens up. Along the way we’ll do a little bit of programming to help explore, give some simple proofs to solidify our intuition, and by the end we’ll see how these ideas can cause wonderful patterns like this one:

mandelbrot
The number i
Let’s bring the story back around to squares. One fact we all remember about numbers is that squaring a number gives you something non-negative.
Let’s see how: if you say that your made-up number
So because it makes no difference (this is what mathematicians mean by, “without loss of generality”) we can assume that the number we’re inventing will have a square of negative one. Just to line up with history, let’s call the new number
is a new number, which we’ll just call . And if we added two of these together, , we can combine the real parts and the parts to get . Same goes for subtraction. In general a complex number looks like , because as we’ll see in the other points you can simplify every simple arithmetic expression down to just one “real number” part and one “real number times ” part.- We can multiply
, and we’ll just call it , and we require that multiplication distributes across addition (that the FOIL rule works). So that, for example, . - Dividing is a significantly more annoying. Say we want to figure out what
is (in fact, it’s not even obvious that this should look like a regular number! But it does). The notation just means we’re looking for a number which, when we multiply by the denominator , we get back to 1. So we’re looking to find out when where and are variables we’re trying to solve for. If we multiply it out we get , and since the real part and the part have to match up, we know that and . If we solve these two equations, we find that works great. If we want to figure out something like , we just find out what is first, and then multiply the result by .
So that was tedious and extremely boring, and we imagine you didn’t even read it (that’s okay, it really is boring!). All we’re doing is establishing ground rules for the game, so if you come across some arithmetic that doesn’t make sense, you can refer back to this list to see what’s going on. And once again, for the purpose of this post, we’re asserting that all these laws hold. Maybe some laws follow from others, but as long as we don’t come up with any nasty self-contradictions we’ll be fine.
And now we turn to the real questions: is
I’ll just let you in on the secret to save us from this crisis. It turns out that
Geometry? How is geometry going to help me understand numbers!?
It’s a valid question and part of why complex numbers are so fascinating. And I don’t mean geometry like triangles and circles and parallel lines (though there will be much talk of angles), I mean transformations in the sense that we’ll be “stretching,” “squishing,” and “rotating” numbers. Maybe another time I can tell you why for me “geometry” means stretching and rotating; it’s a long but very fun story.
The clever insight is that you can represent complex numbers as geometric objects in the first place. To do it, you just think of

single-complex-number
We draw it as an arrow for a good reason. Stretching, squishing, rotating, and reflecting will all be applied to the arrow, keeping its tail fixed at the center of the axes. Sometimes the arrow is called a “vector,” but we won’t use that word because here it’s synonymous with “complex number.”
So let’s get started squishing stuff.
Stretching, Squishing, Rotating
Before we continue I should clear up some names. We call a number that has an
Python is going to be a great asset for us in exploring complex numbers, so let’s jump right into it. It turns out that Python natively supports complex numbers, and I wrote a program for drawing complex numbers. I used it to make the plot above. The program depends on a library I hate called matplotlib, and so the point of the program is to shield you from as much pain as possible and focus on complex numbers. You can use the program by downloading it from this blog’s Github page, along with everything else I made in writing this post. All you need to know how to do is call a function.
Here’s the function header:
# plotComplexNumbers : [complex] -> None
# display a plot of the given list of complex numbers
def plotComplexNumbers(numbers):
...
Before we show some examples of how to use it, we have to understand how to use complex numbers in Python. It’s pretty simple, except that Python was written by people who hate math, and so they decided the complex number would be represented by
So in Python it’s just like any other computation. For example:
>>> (1 + 1j)*(4 - 2j) == (6+2j)
True
>>> 1 / (1+1j)
(0.5-0.5j)
And so calling the plotting function with a given list of complex numbers is as simple as importing the module and calling the function
from plotcomplex import plot
plot.plotComplexNumbers([(-1+1j), (1+2j), (-1.5 - 0.5j), (.6 - 1.8j)])
Here’s the result

example-complex-plot
So let’s use plots like this one to explore what “multiplication by
Even without plotting it’s pretty easy to tell what multiplying by
What’s the pattern in these? well if we plot all these numbers, they’re all at right angles in counter-clockwise order. So this might suggest that multiplication by




Well, it looks close but it’s hard to tell. Some of the axes are squished and stretched, so it might be that our images don’t accurately represent the numbers (the real world can be such a pain). Well when visual techniques fail, we can attempt to prove it.
Clearly multiplying by
Indeed,
This still isn’t all that convincing, and we want to be 100% sure we’re right. What we really need is a way to arithmetically compute the angle between two complex numbers in their plotted forms. What we’ll do is find a way to measure the angle of one complex number with the

angle-example
One way to do this is with trigonometry: the geometric drawing of

triangle-example
And so if
The
>>> import cmath
>>> cmath.polar(1 + 1j)
(1.4142135623730951, 0.7853981633974483)
>>> z = cmath.polar(1 + 1j)
>>> cmath.rect(z[0], z[1])
(1.0000000000000002+1j)
It’s a little bit inaccurate on the rounding, but it’s fine for our purposes.
So how do we compute the angle between two complex numbers? Just convert each to the polar form, and subtract the second coordinates. So if we get back to our true goal, to figure out what multiplication by
def angleBetween(z, w):
zPolar, wPolar = cmath.polar(z), cmath.polar(w)
return wPolar[1] - zPolar[1]
print(angleBetween(1 + 1j, (1 + 1j) * 1j))
print(angleBetween(2 - 3j, (2 - 3j) * 1j))
print(angleBetween(-0.5 + 7j, (-0.5 + 7j) * 1j))
Running it gives
1.5707963267948966
1.5707963267948966
-4.71238898038469
Note that the decimal form of
But we still haven’t proved it works. So let’s do that now. To say what the angle is between
This fact is maybe awkward to write out algebraically, but it’s just saying that if you shift the whole sine curve a little bit you get the cosine curve, and if you keep shifting it you get the opposite of the sine curve (and if you kept shifting it even more you’d eventually get back to the sine curve; they’re called periodic for this reason).
So immediately we can rewrite the second number as
Applying this same idea to
But before we do that we still have one question to address, the question that started this whole geometric train of thought: does
In fact it’s a very deeper and more beautiful theorem (“theorem” means “really important fact”) called the fundamental theorem of algebra. And essentially it says that the complex numbers are complete. That is, we can always find square roots, cube roots, or anything roots of numbers involving
On to pretty patterns!
The Fractal
So here’s a little experiment. Since every point in the plane is the end of some arrow representing a complex number, we can imagine transforming the entire complex plane by transforming each number by the same rule. The most interesting simple rule we can think of: squaring! So though it might strain your capacity for imagination, try to visualize the idea like this. Squaring a complex number is the same as squaring it’s length and doubling its angle. So imagine: any numbers whose arrows are longer than 1 will grow much bigger, arrows shorter than 1 will shrink, and arrows of length exactly one will stay the same length (arrows close to length 1 will grow/shrink much more slowly than those far away from 1). And complex numbers with small positive angles will increase their angle, but only a bit, while larger angles will grow faster.
Here’s an animation made by Douglas Arnold showing what happens to the set of complex numbers

complex-squaring
So that’s pretty, but this is by all accounts a well-behaved transformation. It’s “predictable,” because for example we can always tell which complex numbers will get bigger and bigger (in length) and which will get smaller.
What if, just for the sake of tinkering, we changed the transformation a little bit? That is, instead of sending
Now it’s not so obvious: which numbers will grow and which will shrink? Notice that it’s odd because adding 1 only changes the real part of the number. So a number whose length is greater than 1 can become small under this transformation. For example,
So here’s an interesting question: are there any complex numbers that will stay small even if I keep transforming like this forever? Specifically, if I call
Before we jump to conclusions let’s write a program to see what happens for more than our random guesses. The program is simple: we’ll define the “square plus one” function, and then repeatedly apply that function to a number for some long number of times (say, 250 times). If the length of the number stays under 2 after so many tries, we’ll call it “small forever,” and otherwise we’ll call it “not small forever.”
def squarePlusOne(z):
return z*z + 1
def isSmallForever(z, f):
k = 0
while abs(z) < 2: z = f(z) k += 1 if k > 250:
return True
return False
This isSmallForever
function is generic: you can give it any function abs
function is a built-in Python function for computing the length of a complex number.
Then I wrote a classify
function, which you can give a window and a small increment, and it will produce a grid of zeros and ones marking the results of isSmallForever
. The details of the function are not that important. I also wrote a function that turns the grid into a picture. So here’s an example of how we’d use it:
from plotcomplex.plot import gridToImage
def classifySquarePlusOne(z):
return isSmallForever(z, squarePlusOne)
grid = classify(classifySquarePlusOne) # the other arguments are defaulted to [-2,2], [-2,2], 0.1
gridToImage(grid)
And here’s the result. Points colored black grow beyond 2, and white points stay small for the whole test.

Looks like they'll always grow big.
So it looks like repeated squaring plus one will always make complex numbers grow big. That’s not too exciting, but we can always make it more exciting. What happens if we replace the 1 in
You can randomly guess and see that 0 will never grow big, because
def classifySquareMinusOne(z):
return isSmallForever(z, squareMinusOne)
grid = classify(classifySquareMinusOne)
gridToImage(grid)
And the result:

second-attempt
Now that’s a more interesting picture! Let’s ramp up the resolution
grid = classify(classifySquareMinusOne, step=0.001)
gridToImage(grid)

second-attempt-zoomed
Gorgeous. If you try this at home you’ll notice, however, that this took a hell of a long time to run. Speeding up our programs is very possible, but it’s a long story for another time. For now we can just be patient.
Indeed, this image has a ton of interesting details! It looks almost circular in the middle, but if we zoom in we can see that it’s more like a rippling wave

second-attempt-zoomed2
It’s pretty incredible, and a huge question is jumping out at me: what the heck is causing this pattern to occur? What secret does -1 know that +1 doesn’t that makes the resulting pattern so intricate?
But an even bigger question is this. We just discovered that some values of
Sounds like a job for another program. This time we’ll use a nice little Python feature called a closure, which we define a function that saves the information that exists when it’s created for later. It will let us write a function that takes in
def squarePlusC(c):
def f(z):
return z*z + c
return f
And we can use the very same classification/graphing function from before to do this.
def classifySquarePlusC(c):
return isSmallForever(0, squarePlusC(c))
grid = classify(classifySquarePlusC, xRange=(-2, 1), yRange=(-1, 1), step=0.005)
gridToImage(grid)
And the result:

mandelbrot
Stunning. This wonderful pattern, which is still largely not understood today, is known as the Mandelbrot set. That is, the white points are the points in the Mandlebrot set, and the black points are not in it. The detail on the border of this thing is infinitely intricate. For example, we can change the window in our little program to zoom in on a particular region.

mandelbrot-zoomed
And if you keep zooming in you keep getting more and more detail. This was true of the specific case of
So this is the end of our journey for now. I’ve posted all of the code we used in the making of this post so you can continue to play, but here are some interesting ideas.
- The Mandelbrot set (and most fractals) are usually colored. The way they’re colored is as follows. Rather than just say true or false when zero blows up beyond 2 in length, you return the number of iterations
that happened. Then you pick a color based on how big is. There’s a link below that lets you play with this. In fact, adding colors shows that there is even more intricate detail happening outside the Mandelbrot set that’s too faint to see in our pictures above. Such as this. - Some very simple questions about fractals are very hard to answer. For example, is the Mandelbrot set connected? That is, is it possible to “walk” from every point in the Mandelbrot set to every other point without leaving the set? Despite the scattering of points in the zoomed in picture above that suggest the answer is no, the answer is actually yes! This is a really difficult thing to prove, however.
- The patterns in many fractals are often used to generate realistic looking landscapes and generate pseudo randomness. So fractals are not just mathematical curiosities.
- You should definitely be experimenting with this stuff! What happens if you change the length threshold from 2 to some bigger number? What about a smaller number? What if you do powers different than
? There’s so much to explore! - The big picture thing to take away from this is that it’s not the numbers themselves that are particularly interesting, it’s the transformations of the numbers that generate these patterns! The interesting questions are what kinds of things are the same under these transformations, and what things are different. This is a very general idea in mathematics, and the more math you do the more you’ll find yourself wondering about useful and bizarre transformations.
For the chance to keep playing with the Mandelbrot set, check out this Mandelbrot grapher that works in your browser. It lets you drag rectangles to zoom further in on regions of interest. It’s really fun.
Until next time!
Want to respond? Send me an email, post a webmention, or find me elsewhere on the internet.