Overview
The math utilities in math_utils.c provide essential operations for fractal calculations:
Coordinate system transformations
Complex number arithmetic
Scaling and mapping functions
All functions are defined in fractol.h and implemented in math_utils.c.
Coordinate Mapping
map()
Maps a value from one range to another using linear interpolation.
double map ( double unscaled_num , double new_min , double new_max , double old_max );
The value to map from the old range [0, old_max]
Minimum value of the target range
Maximum value of the target range
Maximum value of the source range (minimum is always 0)
Implementation
From math_utils.c:15-22:
double map ( double unscaled_num , double new_min , double new_max , double old_max )
{
double old_min;
old_min = 0 ;
return ((new_max - new_min) * (unscaled_num - old_min)
/ (old_max - old_min) + new_min);
}
f ( x ) = ( n e w m a x − n e w m i n ) × ( x − 0 ) o l d m a x − 0 + n e w m i n f(x) = \frac{(new_{max} - new_{min}) \times (x - 0)}{old_{max} - 0} + new_{min} f ( x ) = o l d ma x − 0 ( n e w ma x − n e w min ) × ( x − 0 ) + n e w min
This implements linear interpolation from range [0, old_max] to [new_min, new_max].
Usage Examples
Screen X to Complex Plane
Screen Y to Complex Plane (Inverted)
Color Gradient Mapping
// Maps pixel x-coordinate [0, 2000] to complex real axis [-2, +2]
z.x = map (x, - 2 , + 2 , WIDTH);
// With WIDTH = 2000:
// x=0 → -2.0
// x=1000 → 0.0
// x=2000 → +2.0
The source range always starts at 0 (old_min is hardcoded). This simplifies pixel coordinate mapping since screen coordinates start at (0, 0).
Complex Number Operations
sum_complex()
Adds two complex numbers.
t_complex sum_complex (t_complex z1 , t_complex z2 );
First complex number (x component = real part, y component = imaginary part)
Second complex number to add
Returns
Complex number representing z1 + z2
Implementation
From math_utils.c:24-31:
t_complex sum_complex (t_complex z1 , t_complex z2 )
{
t_complex result;
result . x = z1 . x + z2 . x ;
result . y = z1 . y + z2 . y ;
return (result);
}
For complex numbers z 1 = a + b i z_1 = a + bi z 1 = a + bi and z 2 = c + d i z_2 = c + di z 2 = c + d i :
z 1 + z 2 = ( a + c ) + ( b + d ) i z_1 + z_2 = (a + c) + (b + d)i z 1 + z 2 = ( a + c ) + ( b + d ) i
Usage in Fractal Iteration
From fractol_render.c:50:
z = sum_complex ( sqare_complex (z), c);
This implements the core fractal formula: z n + 1 = z n 2 + c z_{n+1} = z_n^2 + c z n + 1 = z n 2 + c
sqare_complex()
Function name contains a typo (“sqare” instead of “square”) in the original source code.
Computes the square of a complex number.
t_complex sqare_complex (t_complex z );
Complex number to square (x = real part, y = imaginary part)
Returns
Complex number representing z²
Implementation
From math_utils.c:33-40:
t_complex sqare_complex (t_complex z )
{
t_complex result;
result . x = ( z . x * z . x ) - ( z . y * z . y );
result . y = 2 * z . x * z . y ;
return (result);
}
For complex number z = a + b i z = a + bi z = a + bi :
z 2 = ( a + b i ) 2 = a 2 + 2 a b i + ( b i ) 2 = ( a 2 − b 2 ) + 2 a b i z^2 = (a + bi)^2 = a^2 + 2abi + (bi)^2 = (a^2 - b^2) + 2abi z 2 = ( a + bi ) 2 = a 2 + 2 abi + ( bi ) 2 = ( a 2 − b 2 ) + 2 abi
Breaking down the components:
Real part : a 2 − b 2 a^2 - b^2 a 2 − b 2 (difference of squares)
Imaginary part : 2 a b 2ab 2 ab (twice the product)
Derivation
Expanding ( a + b i ) 2 (a + bi)^2 ( a + bi ) 2 : (a + bi)² = (a + bi)(a + bi)
= a·a + a·bi + bi·a + bi·bi
= a² + 2abi + b²i²
Since i 2 = − 1 i^2 = -1 i 2 = − 1 : = a² + 2abi + b²(-1)
= (a² - b²) + 2abi
Therefore:
result.x = z.x * z.x - z.y * z.y (real part)
result.y = 2 * z.x * z.y (imaginary part)
Example Calculation
t_complex z = { 3.0 , 4.0 }; // 3 + 4i
t_complex result = sqare_complex (z);
// result.x = 3² - 4² = 9 - 16 = -7
// result.y = 2 × 3 × 4 = 24
// Result: -7 + 24i
Verification: ( 3 + 4 i ) 2 = 9 + 24 i + 16 i 2 = 9 + 24 i − 16 = − 7 + 24 i (3 + 4i)^2 = 9 + 24i + 16i^2 = 9 + 24i - 16 = -7 + 24i ( 3 + 4 i ) 2 = 9 + 24 i + 16 i 2 = 9 + 24 i − 16 = − 7 + 24 i ✓
Usage in Fractal Rendering
These functions work together in the main iteration loop (fractol_render.c:48-58):
Mandelbrot/Julia Iteration
while (i < fractol -> iterations_definition)
{
z = sum_complex ( sqare_complex (z), c); // z = z² + c
if (( z . x * z . x ) + ( z . y * z . y ) > fractol -> escape_value )
{
// Point escapes - calculate color
color = map (i, PSYCHEDELIC_LIME, PSYCHEDELIC_MINT,
fractol -> iterations_definition );
my_pixel_put (x, y, & fractol -> img , color);
return ;
}
i ++ ;
}
Step-by-Step Example
Show Mandelbrot iteration for c = (-0.5, 0.5)
Initial values :
c = − 0.5 + 0.5 i c = -0.5 + 0.5i c = − 0.5 + 0.5 i
z 0 = 0 + 0 i z_0 = 0 + 0i z 0 = 0 + 0 i
escape_value = 4
Iteration 1 (z 1 = z 0 2 + c z_1 = z_0^2 + c z 1 = z 0 2 + c ):z₀² = sqare_complex(0 + 0i) = 0 + 0i
z₁ = sum_complex(0 + 0i, -0.5 + 0.5i) = -0.5 + 0.5i
|z₁|² = (-0.5)² + (0.5)² = 0.5 < 4 ✓ continues
Iteration 2 (z 2 = z 1 2 + c z_2 = z_1^2 + c z 2 = z 1 2 + c ):z₁² = sqare_complex(-0.5 + 0.5i)
= (0.25 - 0.25) + 2(-0.5)(0.5)i = 0 - 0.5i
z₂ = sum_complex(0 - 0.5i, -0.5 + 0.5i) = -0.5 + 0i
|z₂|² = 0.25 < 4 ✓ continues
Iteration 3 (z 3 = z 2 2 + c z_3 = z_2^2 + c z 3 = z 2 2 + c ):z₂² = sqare_complex(-0.5 + 0i) = 0.25 + 0i
z₃ = sum_complex(0.25 + 0i, -0.5 + 0.5i) = -0.25 + 0.5i
|z₃|² = 0.0625 + 0.25 = 0.3125 < 4 ✓ continues
This point likely remains bounded (part of the Mandelbrot set).
Show Julia iteration escaping quickly
Initial values :
c = 0.285 + 0.01 i c = 0.285 + 0.01i c = 0.285 + 0.01 i (Julia constant)
z 0 = 1.5 + 0 i z_0 = 1.5 + 0i z 0 = 1.5 + 0 i (pixel coordinate)
escape_value = 4
Iteration 1 :z₀² = sqare_complex(1.5 + 0i) = 2.25 + 0i
z₁ = sum_complex(2.25 + 0i, 0.285 + 0.01i) = 2.535 + 0.01i
|z₁|² = 6.426225 + 0.0001 > 4 ✗ ESCAPES!
Color calculation: color = map ( 1 , PSYCHEDELIC_LIME, PSYCHEDELIC_MINT, 30 );
// i=1 escaped → darker green (near LIME)
Magnitude Check Optimization
Instead of calculating x 2 + y 2 \sqrt{x^2 + y^2} x 2 + y 2 and comparing to 2, the code compares x 2 + y 2 x^2 + y^2 x 2 + y 2 to 4:
// Efficient: No square root needed
if ((z.x * z.x) + (z.y * z.y) > fractol -> escape_value) // escape_value = 4
// Equivalent but slower:
if ( sqrt ((z.x * z.x) + (z.y * z.y)) > 2.0 )
This avoids expensive square root operations across 3 million pixels.
Function Call Overhead
These functions are called millions of times per frame:
map(): 2 calls per pixel (6M times per render)
sqare_complex(): Up to 30 times per pixel (90M max per render)
sum_complex(): Up to 30 times per pixel (90M max per render)
Consider these candidates for compiler inlining or macro optimization in performance-critical builds.