Enter RPN

R47: A Heart-Shaped Box of Strings and Wires
Login

R47: A Heart-Shaped Box of Strings and Wires

Mandelbrot set on R47

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!

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:

The Program

I now present the actual program, with commentary:

LBL ‘Mbrot’
  LocR 07              ; local numbered registers for the innermost (00) loop; speed and scoping
  RCL ‘MBound’
  STO R.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 R.03             ; mnemonic = Y = 3-armed letter; outer screen loop variable
  TICKS                ; remember starting time, for benchmarking output
  STO R.06

  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 R.04           ; mnemonic = X = 4-armed letter; inner screen loop variable

    LBL i              ; inner screen loop; i = "X"
      0                ; Mandelbrot z starts at 0
      STO R.00         ; mnemonic: z = zero
      RCL ‘Xrange’     ; scale X from screen coords into Mandelbrot range
      RCL÷ ‘SXMax’
      RCL× R.04
      RCL+ ‘Xmin’
      RCL ‘Yrange’     ; ditto Y
      RCL÷ ‘SYMax’
      RCL× R.03
      RCL+ ‘Ymin’
      COMPLEX          ; construct c = Y + Xi
      STO R.01         ; mnemonic: second element in expression
      RCL ‘IterMx’     ; innermost loop to determine if z escapes set
      STO R.05         ; local numbered copy to avoid repeated by-name lookup

      LBL 00           ; the “zero” loop calculates z = .00
        RCL R.00       ; core rule: z ↦ z² + c
        𝑥²
        RCL R.01
        +
        STO R.00
        |𝑥|            ; the vector length of z in rectangular coordinate space
        𝑥>? R.02       ; test against local copy of MBound constant = sqrt(2)
          GTO e        ; it escaped the set!
        DSZ R.05       ; keep looping until we determine z's disposition
          GTO 00
        RCL R.03       ; did not GTO e, so it's in the Mandelbrot set!
        RCL R.04
        PIXEL

        LBL e          ; either falling thru from above or skipped the PIXEL
          DSL R.04     ; next X iteration, inner LBL i
            GTO i
          PAUSE 00     ; let firmware update the LCD per row; needed on HW, not sim
          DSL R.03     ; next Y iteration, outer LBL j
            GTO j
          GTO f        ; finish up
          RTN          ; end of 'e', purely for 'rejig --fmt' purposes
        RTN            ; end of 00 loop, same reason
      RTN              ; end of inner 'i' screen loop, ditto
    RTN                ; end of outer 'j' screen loop, ditto

  LBL f                ; finishing steps
    SNAP               ; save a screenshot of the resulting image
    TICKS              ; get the current time, in ticks (0.1s)
    RCL- R.06          ; subtract starting time from it
    36000.             ; produce seconds in HP decimal format
    ÷
    H→𝕋                ; convert to R47 typed time value to show run time
    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 23 seconds to run on my main macOS desktop machine, on the r47 simulator.1 This is under version 00.109.04.00b0, where several speed improvements to the program interpreter landed.

You might guess that the remaining speed hits come down to 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.

To-Do

This is just a start. Ideas for future expansion:

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


  1. ^ I cheated: leaving the the PAUSE 00 call in adds another 7 seconds for no particularly good reason, but since it is only necessary on the hardware, I removed it.