Mandelbrot set reaches to its limit way too soon

2.5k views Asked by At

im calculating mandelbrot set, with the ability of zooming and printing it to the screen using OpenGL.

as you know mandelbrot set is defined by a rectangle(top right point and bottom left point) and every time i 'zoom in' and 'zoom out' i change the following points by moving the bottom left point to the upper right side and the top right point vice versa. eventually those 2 points are supposed to meet each other and finish the zooming process.
according to many YOUTUBE movies i can see clearly that some movies allow you to reach to 1000X zoom. when in my program i can hardly reach to X6. while debugging i can see my 2 points, which define the mandelbrot set reach each other (x1,y1) = (x2,y2).
few things :
(x1,y1) & (x2,y2) are defined as floats. should i use double instead ? my mandelbrot is defined by (512X512) points. is that enough? though im not sure it's connected to the problem.

another problem i;m facing is that - im printing the set as an height map(3D set). when each Y component representes the amount of iterations it took a certain point to reach infinity. however each time i zoom in the entire set goes upper and upper to the my camera's positions and eventually my camera is consumed by the set. is there a way to calculate the difference and move the camera away from the set(from the zooming point accordingly?)

the code that calcs the set :

double ratiox = instance->foundPointOnHost.x / ((instance->constVal[1][0] - instance->constVal[0][0]));;
            double ratioy = 1-instance->foundPointOnHost.z / ((instance->constVal[1][1] - instance->constVal[0][1]));;
            double xrange = instance->m_GraphConfig.xru-instance->m_GraphConfig.xld;
            double yrange = instance->m_GraphConfig.yru-instance->m_GraphConfig.yld;
            instance->m_GraphConfig.xld += 5*direction*0.005*ratiox*xrange;
            instance->m_GraphConfig.xru -= 5*direction*0.005*(1.-ratiox)*xrange;
            instance->m_GraphConfig.yld += 5*direction*0.005*(1.-ratioy)*yrange;
            instance->m_GraphConfig.yru -= 5*direction*0.005*ratioy*yrange;  

few things :

instance->FoundPointOnHost = the point i want to zoom in.
instance->constVal = contains the original size of the set (in the begining equals to [xru,yru] [xld,yld])
(xru,yru) = top right points of the set (xld,yld) = bottom left points of the set

thanks!

2

There are 2 answers

0
Alnitak On

Yes, if you can you should use double instead of float.

A float only has 24 bits of mantissa, and when multiplying values at the limit of the precision over and over the extra bits generated just get lost.

FWIW, my WebGL mandelbrot generator (which is restricted to float because of WebGL) does manage about 5000x zoom.

3
Chris Nash On

Thanks for including your code! (+1 to the question for making things clear and including all the relevant information).

It shows that your theory for the zoom is correct. At every step, the xrange and yrange will be scaled by a constant factor (0.975?), and the window will move towards foundPointOnHost keeping the same relative position in the window (ratioX and ratioY). That's definitely the right theory behind the zoom. In practice though, how you're implementing the zoom is going to rapidly accumulate rounding errors because of the way floating-point arithmetic works.

My rules of thumb:

  • every time you multiply or divide two floating point numbers, you've got a chance of introducing an error whose size is the last bit of the result;
  • but every time you add or subtract two floating point numbers, you've got a chance of introducing an error whose size is the last bit of the largest operand.

Worked example. Suppose you're zooming into (0.5,0.3), and you're already zoomed so xrange and yrange is about 0.001. And let's say you're using floats (23 bits, about 7 decimal places of accuracy). In your code, when you subtract the two x values to recalculate xrange, you're subtracting two numbers that are both close to 0.5, which means your error in xrange is as much as 0.0000001 - the seventh decimal place of 0.5 - and since the result is about 0.001, your xrange is only precise to four decimal places. Then you scale, ratio, and add xrange back onto the edges of the window (by an amount that's about 0.00025, now), which again are around 0.5, so that arithmetic operation is only precise to 0.0000001 as well, and the window edges themselves are inaccurate from the previous call. Obviously, things are better if you're using doubles, but again, your xrange is losing precision every time through the loop; and by the time you get to the x6 zoom level, perhaps you've lost it all.

The "trick" then is to reduce the number of add and subtracts (currently, you're doing 6 every zoom step), and make sure your errors don't accumulate on every iteration. I'd suggest something like the following: keep the xrange and yrange, and recompute the edges every step: (ratioX and ratioY are constant, and you might need to work out which one is ratio and which is 1-ratio :)

xrange *= 0.975;
xld = foundPointOnHost.x - ratioX * xrange;
xru = foundPointOnHost.x + (1-ratioX) * xrange;

and the same for y. Even better, compute xrange using an operation that won't itself accumulate errors every multiply:

// xrange = INITIAL_RANGE * SCALE_FACTOR^frame_number
xrange = exp ( log(INITIAL_RANGE) + frame_number*log(SCALE_FACTOR) );