Saturday, 15 March 2014

The Mandelbrot set is a fractal which iterates the equation zn+1 = zn² + c in the complex plane and plots which points tend to infinity. Plotting the set with Sinclair BASIC takes over 24 hours so I was curious how much faster it would be in assembly.

It turns out if we use fast 16-bit fixed-point arithmetic we can plot the Mandelbrot in about 5 minutes. To minimise multiplications each iteration is calculated as:

2 comments:

It looks as though you've adapted Gauss's complex multiplication algorithm (see https://en.wikipedia.org/wiki/Multiplication_algorithm#Gauss.27s_complex_multiplication_algorithm) to your need for squaring followed by adding, then streamlined it. But that wikipedia article states "This algorithm uses only three multiplications, rather than four, and five additions or subtractions rather than two. If a multiply is more expensive than three adds or subtracts, as when calculating by hand, then there is a gain in speed. On modern computers a multiply and an add can take about the same time so there may be no speed gain." If there is a gain, you could gain even more by replacing that multiplication by 2 with a doubling by addition (I haven't looked at your code, maybe you already did).

It also looks as though your test for unbounded divergence is based on the result that "... if the absolute value of P_c^n(0) ever becomes larger than 2, the sequence will escape to infinity" (see https://en.wikipedia.org/wiki/Mandelbrot_set#Basic_properties), with you simply testing for your simpler norm at a value that always lies outside the cartesian norm of 2 (since anything passing your test has met the requirements of the result, and anything that diverges unboundedly must necessarily get past your norm's boundary at some point). You could possibly simplify your test further by substituting a slightly larger but rational (better still, dyadic rational, i.e. with finitely many non-zero binary places) test criterion not involving the square root of 2 (again, maybe you already did).

You can speed it up considerably by not using multiplications at all, since you're just suqaring anyway. Squares can be determined from a lookup table, which, in case of the kind of 16-bit arithmetics that you use takes up 32 kilobytes and can be generated in a blink of an eye.

Thus:rₙ₊₁ = x + (rₙ)² - (iₙ)²iₙ₊₁ = y + (rₙ + iₙ)² - [(rₙ)² + (iₙ)²]

That makes a total of 3 lookups. Moreover, if you calculate the Euclidean norm [(rₙ)² + (iₙ)²] first, it can be used to abort the iteration if it grows beyond 4.