Jonathan. Frech’s WebBlog

Mandelbrot set miscalculations (#150)

Jonathan Frech,

While developing a Java pro­gram to create an image of the Mandelbrot set, I stumbled upon a small error which completely changes the set’s look. To fix this bug, you need to swap two lines of code.

The bug arises when trying to convert convenient Python features to Java.
To iteratively ap­ply the func­tion $z\mapsto z^2+c$$z\mapsto z^2+c$$z\mapsto z^2+c$, you update your complex num­ber 𝑧 a certain amount of times. When you are not using a complex num­ber class, but in­stead you use two float­ing point numbers (in Java doubles to gain precision) a and b to define the real and imaginary part ($z=\texttt{a}+\texttt{b}\cdot i$$z=\texttt{a}+\texttt{b}\cdot i$$z=\texttt{a}+\texttt{b}\cdot i$), logically both numbers need to be updated.
In Python you may write the following, when 𝑐 is defined as being a complex num­ber with parts c and d ($c=\texttt{c}+\texttt{d}$$c=\texttt{c}+\texttt{d}$$c=\texttt{c}+\texttt{d}$).

a, b = a**2 - b**2 + c, 2 * a * b + d

Which seems to be very similar to those two lines.

a = a**2 - b**2 + c
b = 2 * a * b + d

But no­tice that in the first code snippet you define a tuple consisting of the real and imaginary part and then assign it to the variables. The first snippet really looks like this.

t = (a**2 - b**2 + c, 2 * a * b + d)
a, b = t

Using this assignment of variables, which corresponds to $z\mapsto z^2+c$$z\mapsto z^2+c$$z\mapsto z^2+c$, you get an image of the correct Mandelbrot set.

In contrary, the second code snippet assigns the old a its new value, then uses this new a to define the value of new b, thus does not cal­cu­late $z\mapsto z^2+c$$z\mapsto z^2+c$$z\mapsto z^2+c$, which is eq­uiv­a­lent to $z\mapsto (\texttt{a}^2-\texttt{b}^2+\texttt{c})+(2\cdot\texttt{a}\cdot\texttt{b}+\texttt{d})\cdot i$$z\mapsto (\texttt{a}^2-\texttt{b}^2+\texttt{c})+(2\cdot\texttt{a}\cdot\texttt{b}+\texttt{d})\cdot i$$z\mapsto (\texttt{a}^2-\texttt{b}^2+\texttt{c})+(2\cdot\texttt{a}\cdot\texttt{b}+\texttt{d})\cdot i$, but rather $z\mapsto (\texttt{a}^2-\texttt{b}^2+\texttt{c})+(2\cdot\texttt{a}^2\cdot\texttt{b}-2\cdot\texttt{b}^3+2\cdot\texttt{b}\cdot\texttt{c}+\texttt{d})\cdot i$$z\mapsto (\texttt{a}^2-\texttt{b}^2+\texttt{c})+(2\cdot\texttt{a}^2\cdot\texttt{b}-2\cdot\texttt{b}^3+2\cdot\texttt{b}\cdot\texttt{c}+\texttt{d})\cdot i$$z\mapsto (\texttt{a}^2-\texttt{b}^2+\texttt{c})+(2\cdot\texttt{a}^2\cdot\texttt{b}-2\cdot\texttt{b}^3+2\cdot\texttt{b}\cdot\texttt{c}+\texttt{d})\cdot i$.

In Java it would look like this.

a = a*a - b*b + c;
b = 2 * a * b + d;

Which results in this rather unusual depiction of the famous fractal.

You can easily avoid this bug when using two sets of variables to define old 𝑧 and new 𝑧, as shown in the following.

_a = a*a - b*b + c;
_b = 2 * a * b + d;
a = _a;
b = _b;

Or you can define variables $\texttt{asqr}=\texttt{a}^2$$\texttt{asqr}=\texttt{a}^2$$\texttt{asqr}=\texttt{a}^2$ and $\texttt{bsqr}=\texttt{b}^2$$\texttt{bsqr}=\texttt{b}^2$$\texttt{bsqr}=\texttt{b}^2$ and swap the assignment. Using variables for the squares of the parts of 𝑧 also helps to improve per­for­mance⁠¹.

b = 2 * a * b + d;
a = asqr - bsqr + c;
asqr = a*a;
bsqr = b*b;

Source code: mandel.java


[1][2020-07-28] Assuming the com­pil­er was not able to op­ti­mize the given source code. Per­for­mance statements should al­ways be delivered on the basis of real-world test­ing, which was not per­form­ed in this case.