Partly because of when I started writing code (the 1970s), and partly because of money, a lot of my early coding was in very constrained environments. I've written before about programming some industrial machinery using a KIM-1. However, the most constrained environment I've coded on is the Commodore P50 programmable calculator in the late 1970s.
Its programming capabilities are very, very limited. There's a single 'register' (the standard memory function of the calculator) and only 24 steps (which are for the most part single presses of a key) are programmable. For example, if your program needs the constant 1234 that's four steps out of 24 taken up!
It was great for writing a program that is just a function that you need to evaluate for multiple values of x. For example, you can store the function sin(1/x) like this:
lrn
1/x
SIN
R/S
GOTO 00
lrn
And then enter a number and press R/S to evaluate sin(1/x). lrn puts the calculator in programming mode and then can accept up to 24 keystrokes. R/S causes the program to terminate.
Adding in line numbers shows that the GOTO takes the program back to the start. This is done because if you run a program and hit R/S again it continues where it left off which could result in an error if the program previously terminated in the middle somewhere.
lrn
00 1/x
01 SIN
02 R/S
03 GOTO
04 00
lrn
Notice how GOTO 00 takes two of 24 steps!
Loops
You can easily write simple loops. For example, if you wanted to plot sin(1/x) for x starting at 0.001 in increments of 0.001 you'd write (STO stores a number in the memory, RCL gets it from memory)
lrn
00 0
01 STO
02 RCL
03 +
04 .
05 0
06 0
07 1
08 =
09 STO
10 1/x
11 SIN
12 R/S
13 GOTO
14 02
lrn
This program uses the memory to store 0.001, 0.002, 0.003, ... and stops outputting sin(1/0.001), sin(1/0.002), sin(1/0.003), ... Hitting R/S obtains the next result.
Conditional branching
The calculator also has three conditional branch operations SKZ, SKN, SKP (skip zero, skip negative, skip positive) which simply skip the next instruction if the displayed number is zero, negative or positive. Here's a program that calculates x modulo y. x is the number on screen when the program is run and y should be stored in the memory beforehand.
Since SKZ, SKN, SKP only skip the next instruction they are most commonly followed by a GOTO somewhere else.
lrn
00 -
01 RCL
02 =
03 SKN
04 GOTO
05 00
06 +
07 RCL
08 =
09 R/S
10 GOTO
11 00
lrn
This program runs extremely slowly even though it just does integer addition and subtraction. 89 modulo 5 takes almost 12 seconds to calculate.
Is this number a prime?
But I'm not letting that deter me. Here's a complete program that returns 1 if the number being tested is prime, or returns a number that divides it if not.
The program divides the x by numbers from sqrt(x) to 2 looking for a divisor.
lrn
00 RCL
01 -
02 INT
03 *
04 4
05 10^x
06 ➗
07 RCL
08 INT
09 -
10 INT
11 =
12 SKZ
13 GOTO
14 18
15 RCL
16 INT
17 R/S
18 1
19 +/-
20 M+
21 GOTO
22 00
lrn
Unfortunately, it requires a little bit of set up by the user. Because the calculator has only one register (the memory) I am forced to store both the original number, x, being divided, and the current divisor, d, being tested in the memory. This is done by storing d + x/10000 in the memory. Thus the program only works to test numbers up to 9,999 (it can easily be extended by changing the number in step 4 above).
Lines 00 to 08 retrieve the number and trial divisor from memory and divides the former by the latter. Lines 09 to 12 determine whether the result is an integer or not (by subtracting the integer part of the result from itself). Lines 15 to 17 are where the program halts when a divisor is found. Lines 18 to 22 subtract one from the trial divisor and loop back to the start.
So, suppose the program is testing if 11 divides 198. The memory would contain 11.0198. Here's what's on screen at each program step (note that the display does not show anything when really running a program but will if you use the single step, SSTP, key)
Since 11 does divide 198 the program will terminate at step 17.
00 RCL 11.0198
01 - 11.0198
02 INT 11
03 * 0.0198
04 4 4
05 10^x 10000
06 ➗ 198
07 RCL 11.0198
08 INT 11
09 - 18
10 INT 18
11 = 0
12 SKZ
13 GOTO
14 18
15 RCL 11.0198
16 INT 11
17 R/S 11
18 1
19 +/-
20 M+
21 GOTO
22 00
Now, here's what happens when testing if 12 divides 198 (which it doesn't). The memory would contain 12.0198. Once again, here's the step by step.
00 RCL 12.0198
01 - 12.0198
02 INT 12
03 * 0.0198
04 4 4
05 10^x 10000
06 ➗ 198
07 RCL 12.0198
08 INT 12
09 - 16.5
10 INT 16
11 = 0.5
12 SKZ
13 GOTO
14 18
15 RCL
16 INT
17 R/S
18 1 1
19 +/- -1
20 M+ -1
21 GOTO
22 00
This time the SKZ doesn't happen so the GOTO 18 occurs. The memory is decremented by 1 and the program loops back to the start with 11.0198 in memory.
To use the program the operator needs to store the number being tested and its square root in the memory. Thus they need to perform the following to set up the memory correctly after entering the number they want tested.
STO
√x
INT
*
4
10^x
=
M+
4
+/-
10^x
Mx
They can then hit R/S and wait... a long time. For example, determining if 199 is prime (it is) takes... 1m44s.
But, at least I know it is possible to fit a primality testing program in 23 steps with a single register.