Motivation
Less than two months before Christmas 2025, I got myself a gift: an R47 from the first production batch.
But what to do with it?
Hey look, it has first-class complex arithmetic, it’s programmable, it allows free-form plotting on its dot-matrix screen… What does that remind me of?
Oh…that!
Caveat RPNer
This program has spotlit several weaknesses in the R47 code, which are presently being looked at.1 Until someone gets around to fixing them, beware that:
If you hit Ctrl-S in the simulator to save a screen snapshot, you lose the image in progress because that forces a screen repaint, and the R47 code isn’t double-buffering this plot output. The same occurs with the keyboard hotkey on the actual R47 hardware.
If you add a manual
SNAPcall at the end of the program to the same end, the same thing occurs, yielding a snapshot of the final stack state.A manual screenshot of the simulator using your OS’s mechanisms — ⌘-Shift-5 on macOS; PrScr on Windows — captures the stray
Pprogram running status indicator, plus likely the current date if your run happens to cross the 1-minute update mark.A “wait for any key” loop to allow indefinite screenshot time also causes a screen refresh.
The workaround is to take the screenshot while it is drawing the last row or two, which is fine because our resolution doesn’t allow important detail to show there.
(All the junk in the screenshot at the top of this article — menus and old stack values — is due to a mishandling of CLLCD in the early versions of this program. I’ve chosen not to replace it with a “clean” version because it is less visually interesting. This is the “happy accident” artistic version. One way to split the difference is to replace the {0, 0} argument to the CLLCD call at the top of the program with {69, 0} to preserve an “interesting” menu like CPX by leaving the 69 rows at the bottom untouched. Each menu row is 23 px high.)
Initialization
To allow easier adjustments, I have broken out the program’s initialization routine as separate:
LBL ‘MbInit’
-3
STO ‘Xmin’
0.75
STO ‘Xmax’
1.12
STO ‘Ymax’
CHS
STO ‘Ymin’
400
STO ‘SXMax’
240
STO ‘SYMax’
25
STO ‘IterMx’
2
√𝑥
STO ‘MBound’
RTN
This must be called once before you execute the program for the first time. After that, these global variables may either be treated as constants or adjusted to suit:
- Ymin, Ymax: the vertical limits of the Mandelbrot set, straight from Wikipedia
- Xmin, Xmax: the horizontal limits, extended from the recommended [-2, 0.47] to account for the R47’s screen aspect ratio, to avoid distortion; incidentally makes the program run faster as more pixels end up outside the set
- SXMax, SYMax: screen dimensions; saved as constants because they’re needed twice in the program, once during setup and once again inside the main loop
- IterMx: maximum number of inner loop iterations before a point is considered inside the Mandelbrot set; the default is fast-ish but a bit sloppy; additional detail can be seen up to 100 or so, at a cost to run time; you can halve it without losing recognizability, but 10 is too far
- MBound: an empirical constant to determine whether the z function is escaping the set or not; worst practical case is a 1-1-sqrt(2) right triangle where the z vector is the hypotenuse; can be as high as 2 before it gives no more detail; waiting until it hits ±Inf is theoretically correct but vastly overkill
The Program
I now present the actual program, with commentary:
LBL ‘Mbrot’
LocR 06 ; local numbered registers for the innermost (00) loop; speed and scoping
RCL ‘MBound’
STO .02 ; mnemonic: it's set to sqrt(2) above in MbInit
0 ; clear entire screen
ENTER ; nonzero (X,Y) means leave part alone
CLLCD
RCL ‘Xmax’ ; calculate X axis range from MbInit values
RCL ‘Xmin’
-
STO ‘Xrange’
RCL ‘Ymax’ ; ditto Y axis range
RCL ‘Ymin’
-
STO ‘Yrange’
RCL ‘SYMax’ ; R47 screen coordinate system puts 0 on the bottom, not the top
DECR X ; convert 1-based screen Y dim to 0-based pixel coord
STO .03 ; mnemonic = Y = 3-armed letter; outer screen loop variable
LBL j ; outer screen iteration loop: j = "Y"
RCL ‘SXMax’ ; plot from rightmost pixel for simpler "countdown" loop logic
DECR X ; convert 1-based screen X dim to 0-based pixel coord
STO .04 ; mnemonic = X = 4-armed letter; inner screen loop variable
LBL i ; inner screen loop; i = "X"
0 ; Mandelbrot z starts at 0
STO .00 ; mnemonic: z = zero
RCL ‘Xrange’ ; scale X from screen coords into Mandelbrot range
RCL÷ ‘SXMax’
RCL× .04
RCL+ ‘Xmin’
RCL ‘Yrange’ ; ditto Y
RCL÷ ‘SYMax’
RCL× .03
RCL+ ‘Ymin’
COMPLEX ; construct c = Y + Xi
STO .01 ; mnemonic: second element in expression
RCL ‘IterMx’ ; innermost loop to determine if z escapes set
STO .05 ; local numbered copy to avoid repeated by-name lookup
LBL 00 ; the “zero” loop calculates z = .00
RCL .00 ; core rule: z ↦ z² + c
x²
RCL .01
+
STO .00
|x| ; the vector length of z in rectangular coordinate space
x>.02? ; test against local copy of MBound constant = sqrt(2)
GTO e ; it escaped the set!
DSZ .05 ; keep looping until we determine z's disposition
GTO 00
RCL .03 ; did not GTO e, so it's in the Mandelbrot set!
RCL .04
PIXEL
LBL e ; either falling thru from above or skipped the PIXEL
DSL .04 ; next X iteration, inner LBL i
GTO i
PAUSE 00 ; let firmware update the LCD per row; needed on HW, not sim
DSL .03 ; next Y iteration, outer LBL j
GTO j
RTN
END
You may now wish to download this separately in R47 program form, suited to copying into PROGRAMS folder on the R47’s USB storage. Eject the disk, then 🟦 I/O READP it into the calculator.
Alternately, call 🟦 I/O SAVEST to back up your calculator’s state, then LOADST this version. Beware! This will RESET your R47’s running state, not quite to factory condition, but leaving only the contents of the flash storage untouched. Thus the backup.
Making It Faster
This program takes about 35 seconds to run on my main macOS desktop machine, in the r47 simulator.2
You might guess the problem is all the high-precision complex number arithmetic, but let’s measure instead.
On profiling it, what we actually find is that the single biggest hot spot is the per-instruction runFunction() call. In other words, it is spending nearly all its time interpreting our program, not actually doing math.
In prior versions of this program, the primary hot spot was findNamedVariable() which looks up the register index for a named variable, but that was because we were doing this multiple times inside the innermost loop, which runs roughly 100k times in this program: 400 px wide × 240 px high × average iteration count of “z is escaping the set” test per pixel. The LocR call at the top let me to move the six variables/constants accessed inside the 00 loop out into a local scope where all accesses are by index, which is much faster. As a bonus, we avoid polluting the global namespace with these innermost variables.
Measurement shows that the remaining by-name variable lookups take ~6% of the program’s running time, but one must ask how important saving that fraction is versus keeping the program readable. I believe what I have now is a good balance.
We could wish for more speed without hand-optimization, but that would require adding a bytecode compiler to the R47, at minimum. I would expect low-hanging fruit resulting from such a project to produce speedups in the 10× range before one had to begin resorting to crazy micro-optimizations to make progress. Beyond that, you could get into JIT and such, but that is highly unlikely to happen.
Another possible speedup is converting the R47 internals to normalized Unicode instead of the idiosyncratic text string format currently used. This would be a tremendous amount of work, but it would let the project switch to IBM’s libICU, the parent project for the decNumber library it already uses. That should not only be better-optimized than this one-off code, it means routines like XPORTP can export to straight Unicode text files instead of relying on RTF to provide mappings into a special font to get readable output.
To-Do
This is just a start. Ideas for future expansion:
- interactive center point movement and zooming
- freeze the display until either user presses a key or we
SNAPa BMP - automatic dive mode, saving BMP at each step; try seahorse valley!
- Julia set mode
Closing Thought
“Pathological monsters!” cried the terrified mathematician. “Every one of them is a splinter in my eye.”
— Jonathan Coulton, Mandelbrot Set
(You may now wish to return to my R47 article index.)
License
This work is © 2025-2026 by Warren Young and is licensed under CC BY-NC-SA 4.0
- ^ Track this issue if you are interested in helping, or at least watching the progress.
- ^
I cheated: leaving the the
PAUSE 00call in adds another 7 seconds for no particularly good reason, but since it is only necessary on the hardware, I removed it.