Double-Precision Mandelbrot

Fractals

The Mandelbrot set computed live on a Super Nintendo in 64-bit IEEE-754 double. The 65816 has no FPU, so every z² + c multiply and add is a soft-float libcall__muldf3, __adddf3, __gtdf2 — over a register pair-of-pairs. The top half is rendered in double; the bottom half in the 32-bit float of demo #21. At whole-set scale the two precisions agree pixel-for-pixel — the set is symmetric about the real axis, so the seam is invisible, and that is exactly the point: the escape buffer is bit-for-bit identical to host x86. Runs automatically; no joypad needed. Written in C and compiled with the llvm-mos-based 65816 toolchain (+mos-a16).

loading core…

Self-running demo — the set grinds in top-to-bottom (the double top half is visibly slower than the float bottom), then spins while it re-computes. (Boot takes a moment to grind the first 64-bit floats.)

Click the screen to focus, then watch. Tab away and it pauses.

What it is

Escape-time Mandelbrot: for each point c, iterate z ← z² + c from z = 0 and colour by how fast |z| escapes. Demo #21 does it in 32-bit float; this one does the top half in 64-bit double, which on a CPU with no floating-point hardware means software routines over four 16-bit words:

mandel_cell(cr, ci):           // one op per statement — no FMA to fuse
  zr = zi = 0
  repeat:
    zr2 = zr*zr                 // __muldf3
    zi2 = zi*zi                 // __muldf3
    if zr2 + zi2 > 4.0: break   // __adddf3, __gtdf2
    zr,zi = (zr2 - zi2) + cr,   // __subdf3, __adddf3
            (zr*zi + zr*zi) + ci // __muldf3, __adddf3

The 64×56 escape buffer lives in high WRAM (the 24-bit far store/load path, which is why the ROM is +mos-a16-only) and is blitted into Mode 7 character VRAM, framed 4× to fill the screen; the hardware affine matrix spins it while the palette cycles. Each pass re-grinds the set, so every frame is a fresh batch of 64-bit multiplies and adds.

Compiler stress-test #33 — 64-bit double soft-float

A Round 3 demo — each Round-3 entry targets a codegen corner none of the first 32 execute. Here it is the double-precision soft-float library: __muldf3, __adddf3/__subdf3, __gtdf2, __floatsidf — plus __truncdfsf2/__extendsfdf2 for the float twin. That 64-bit-float family is disjoint from #22's 64-bit-integer family (__muldi3/__udivdi3) and is otherwise untested by the battery.

ItemWhat it exercises
No FPU — every op a 64-bit libcallThe 65816 has no floating-point unit, so each IEEE-754 double operation is a soft-float library call over a register pair-of-pairs: __muldf3 (z·z, zr·zi), __adddf3/__subdf3 (the z²+c recurrence), __gtdf2 (the |z|²>4 escape test), __floatsidf (pixel index → double). The 64-bit-float family no other demo touches
Bit-exact differentialIEEE-754 double +,−,× and the comparisons are correctly rounded (round-to-nearest-even), so a conforming soft-float on the 65816 must equal host x86 double bit-for-bit. FMA contraction is forbidden by construction (one op per statement, no a·b±c), the oracle built -ffp-contract=off. Verified host == default == +mos-a16 == +mos-xy16 == gate CRC 0x0EDF
Double beside a float twinThe top half is rendered in 64-bit double, the bottom half in the 32-bit float of demo #21 — the set is symmetric about the real axis, so the two halves join seamlessly. At whole-set scale the precisions agree pixel-for-pixel (the seam is invisible — that IS the result); casting the shared double window to float coords also exercises __truncdfsf2
The precision cliffFloat and double only diverge at EXTREME zoom (re_span ~1e-6), where the per-pixel step drops below float’s ~6e-8 epsilon and the float side collapses into chunky blocks while double stays crisp. Resolving that depth needs hundreds of iterations per pixel — tens of thousands of 64-bit libcalls each — so it is far too slow to render live; the gate folds both a double and a float buffer to catch any divergence numerically
A bit-exact orbit witnessBeyond the escape buffers, the gate iterates the recurrence at an interior point and folds the RAW 64-bit bits of |z|² each step (a union type-pun) into an FNV accumulator — the sharp “double is fully specified” claim: a single-ULP slip in any __muldf3/__adddf3 would change the hash

The escape kernel is a portable C header (examples/65816/mandel-double.h) linked into the SNES ROM, a host oracle, and the corpus differential slice alike. The gate folds a small double escape buffer, a float twin buffer, a bit-exact 64-bit orbit witness and a double↔float conversion witness into corpus_result. Because double arithmetic is correctly rounded, that 16-bit hash must be identical on host x86 (native double) and on the console — and it is, across every target codegen mode (default 8-bit, +mos-a16, +mos-xy16), bit for bit.

Written in C with the llvm-mos 65816 toolchain. Hit Verify fidelity to reproduce the build gate's WRAM assert (gate CRC 0x0EDF) live in this tab — the same hash the host oracle and the cycle-accurate bsnes-jg core agree on, bit for bit. The disasm gate proves the shape: __muldf3 = 8, __adddf3/__subdf3 = 12, rep/sep = 31 — 64-bit doubles in native-16 mode.