The Sinclair Scientific is one of my favorite calculators, though certainly not for its speed, accuracy, or feature set. In fact, in an era where full-featured scientific calculators can be had for under ten bucks, it’s a downright laughably bad machine. But it’s evidence of the ingenuity of Sinclair in their race to have made tech accessible for those with slimmer wallets. The Scientific may well be a post for another day, but recently I fell into another ridiculously quirky Sinclair calculator, the Scientific Programmable. Its manual describes it as ‘the first mains/battery calculator in the world to offer a self-contained programming facility combined with true scientific functions at a price within the reach of the general public’.
As with the Scientific, that last bit is key – this machine was engineered to meet a price point. HPs of the day were engineered for speed and accuracy, and were beautiful, easily operated machines to boot. Sinclairs were affordable, period. To start investigating this thing’s quirks, let’s address the ‘true scientific functions’ that the calculator includes. Sine, cosine, arctan, log, and antilog. No arcsine, arccos, or tangent – instead the manual tells you how to derive them yourself using the included functions. Precisely what I expect from a Sinclair (though the aforementioned Scientific did include all standard trig functions).
The highlight (if you will) of this calculator is, of course, its ‘self-contained programming facility’, which is really what I’d like to discuss. While the terms are oft indistinguishable nowadays, I would really consider the Scientific Programmable’s functionality more of a macro recording system than anything resembling programming. There are no conditionals, there is no branching, and a program can only contain 24 keystrokes. The keyboard is shifted for program entry, and integers thus require two extra keystrokes as they are delimited. I say integers because that is all one can enter during program entry – if your program requires you to multiply by .125, you would need to calculate that with integer math first.
My go-to demo program is Viète’s formula for pi. It’s simple, requiring very little in the way of scientific functions, stack manipulation, memory registers, or instructions; yet it’s fun and rewarding. Unfortunately, I don’t actually think it’s possible on the Scientific Programmable, primarily due to the lack of a stack and the single memory register. I just need one more place to stick a value, and a stack would be ideal – it would contain the previous result ready to be multiplied by the next iteration.
We could try pi the Leibniz (et al.) way, 1 – 1⁄3 + 1⁄5 – 1⁄7 + 1⁄9, and so on. But we still need to store two variables – the result of every go, and the counter. I still don’t think it can be done.
How about we eschew pi and just make 3. Easy enough, just type
3 or, perhaps
69 enter 23 ÷. But what if I want to do it Ramanujan-style with more nested radicals? I… still don’t think I can, because again I essentially need a decrement counter. Bit of the inverse of the problem as above, one place for storage just isn’t enough. Sorry, Ramanujan.
So what can we do? I guess the golden ratio is simple enough:
' 1 ' + √ and then just mash
EXEC repeatedly until we have something resembling 1.618. Not terribly satisfying. Also, the calculator lacks the precision to ever actually make it beyond 1.6179.
To be fair, the calculator (well, not mine, but a new one) comes with a program library in addition to the manual. Katie Wasserman’s site has them, fortunately. And while none of the programs are particularly interesting in any sort of technical way, they do give a good overview of how this macro mentality would cut down on repetitive calculations. One thing that I do find technically interesting, from a small systems/low level perspective is Sinclair’s advice on dealing with the limitations. For instance, they mention that pi is 355⁄113 which yields 3.1416, as accurate as is possible. But if one is willing to deal with less accuracy, they suggest 4*(arctan 1) for 3.1408 (~.02%) or 22⁄7 for 3.1428 (~.04%). Determine needs and spend memory accordingly.
All in all, I don’t know what I’ll do with the Scientific Programmable beyond occasionally pulling it out to mess with. It’s not really fun to program like an old HP, because it’s just too limited. I guess if I come up with any other simple, iterative formulas that I can plug into it, I may revisit. But, much like the Sinclair Scientific, it will largely stay in my collection as a quirk, a demonstration of what was ‘good enough’ alongside the cutting edge.
After working on the nearest Fibonacci number problem in
dc, I got to thinking about how one might implement other generalized Fibonacci sequences in the language. Nth-order sequences seemed like a fun little challenge, and since I was simply doing it for my own pleasure I went ahead and gave it prompts and user input:
% dc -e '[_nacci? ]n?1-sg[terms? ]n?2-snlgsi[0lid1-si1<Z]dsZx1[dls+ssli:flid1+silg>G]sG[li;flid1-si0<R]sRlgsz[0si0sslGxlgsilRxlslzd1+szln>M]dsMxf'
…gives us the first 20 terms of the tetranacci sequence. This interested me because unlike a simple Fibonacci iteration that can be handled on the stack with only rudimentary stack manipulation (
dsf+lfr), higher order ‘naccis need summation of more terms. For a defined number, I could simply use registers, but
dc does support arrays, so setting the order at runtime is plausible. There’s a bit going on here, so I’m going to start by rattling off all of the registers I use:
- The order (so, a g-nacci sequence)
- Number of terms to run through
- General counter
- Array used to temporarily hold the last sequence as it’s being summed
- Generalized Fibonacci generating macro
- Macro to retrieve previous sequence from f
- Zero-seed macro
- Counter for iterations (compares to n)
- Main macro
Now to break down what’s going on.
[_nacci? ]n?1-sg[terms? ]n?2-sn just prompts the user and inputs
n. We reduce each of these in the process to appease the loops. After doing this, we need to seed the sequence with
g-1 zeroes, and one one.
lgsi sets counter
g, and then the macro
Z, does nothing but put a zero on the stack and loop:
dsZx stores the macro and executes it; then
1 pushes the necessary one onto the stack such that we can begin.
[dls+ss]sS is our macro,
S, which is a simple summer for register
s. It duplicates the top of stack, recalls
s, adds the two together, and then writes that back to
s. The stack is returned to its original state.
Our next macro,
G, has a bit more going on:
[dls+ssli:flid1+silg>G]sG. It starts with a simple summer for register
dls+ss. This duplicates the stack, recalls
s, adds them and then overwrites
s with the new sum. The stack returns to its original state. The next thing we need to do is move the top of the stack into our holding array,
f. We’ll use our counter
i as an index, so we load
i and then do the array operation,
li:f. Every time we do these two things, our summation (the next number in the sequence) nears its next value, and our stack shrinks. The rest of the macro,
lid1+sig>G just handles incrementing
i and comparing it to our order,
g, determining whether or not to continue the loop.
[li;flid1-si0<R]sR repopulates the stack from array
f. Before calling
i is set to
g, and we use that as our array index to pull from, thus treating the array as a LIFO.
li;f does this, and then the rest of the macro is (surprise, surprise) counter/loop handling.
Before we run macro
M, which is our main macro essentially, we set counter
z to our order number
g, which accounts for the fact that we already have our first few terms in assorted zeroes and a one.
[0si0sslGxlgsilRxlslzd1+szln>M]dsMx, starts out by resetting counter
i and the sum register
s to zero:
lGx runs macro
lgsi sets counter
i to our order
g, and then
lRx runs macro
ls puts our sum (the new Fibonacci value) at the top of the stack, and then the rest of the thing is counter/loop handling.
dsMx saves the macro as
M and also sets it running, while our last command,
f prints the entire stack.
I hadn’t really exercised my code golf skills in a while, but saw a fun challenge today that seemed like an easy solve, well-suited for
dc. The challenge was, given a positive integer, return the nearest Fibonacci number. The Fibonacci sequence is fun on its own in a stack language; in
dc one step can be performed with something like
dsf+lfr. Given that the stack is prepared with two integers, this will duplicate the top, store it in an intermediate register
f, add the two previous values, push
f back onto the stack, and then swap them such that they’re in the correct order. It doesn’t pollute the stack, either, these two values are all that remain.
For this challenge, those two values are all I ever need – my input (which I will refer to as
i) is always going to fall between two consecutive numbers in the Fibonacci sequence (or on one of them, in which case I would only need that value to test against). Keeping as much work on the stack as possible is ideal when golfing in
dc because the byte cost of working with registers adds up quickly. So my strategy is to seed the Fibonacci generator with two 1s, and run it until the larger of the two Fibonacci numbers is greater than
i. One of those two Fibonacci numbers will be the right one, and if
i happened to be a Fibonacci number, I’ve just generated an extra one for no reason. I convert both of the Fibonacci numbers to their respective differences from
i. Since I know for a fact that the top of the stack is greater than
i, and the second value on the stack is either less than or equal to
i, I don’t have to worry about
dc’s lack of an absolute value mechanism; I simply subtract
i from the big one and subtract the small one from
i. Since I know which difference is which, I have no need to retain the Fibonacci numbers. I simply compare the differences, and then reconstruct the Fibonacci number by adding or subtracting the difference to
i depending on which difference ‘won’. The code:
…and the explanation:
?si Store user input in register i
1d Push 1 to stack, duplicate it to seed Fibonacci sequence
[dsf+lfrdli>F]dsFX Macro F: Fibonacci generator as described above, with the
addition of loading register i and continuing to run F
until the top-of-stack Fibonacci number is larger than i
li- Turn our high Fibonacci number into its difference
rlir- Swap the stack, and turn our low Fibonacci number into its
difference. The stack stays swapped, but it doesn't
sd Store our low Fibonacci difference in register d
[lild-pq]sD Macro D: reconstruct the low Fibonacci number by
subtracting d (the difference) from i; print the result
and quit. Macro is stored for later use.
dld<D Duplicate the high Fibonacci difference and push the low
Fibonacci difference onto the stack. Compare them and run
D if the low difference is less than the high difference
li+p Else, add i to the high difference and print the result
Inspired by this recent post and code snippet by John Gruber, I decided to have a go at outputting Collatz conjecture sequences in
dc -e '?[r3*1+]so[d2~1=opd1<x]dsxx'
from the command line will take a single value as input, and run the sequence.
? takes input, puts it on the stack.
[r3*1+]so is our macro for n3+1, what we do when we encounter an odd value.
[d2~1=opd1<x]dsxx is our main macro, as well as our macro for handling even numbers (n/2). First it duplicates the value on the stack (last iteration) and divides by 2 to return the quotient and the remainder.
1=o uses the remainder to test for oddness and run
o if true. Here I do something rather lazy for the sake of concise code: that initial duplication was solely for the sake of oddness.
o swaps the top of the stack to get that value back, then multiplies by three and adds one, leaving this value on the stack. Evenness will never run this, and since the test for evenness leaves the outcome for evenness on the stack (what luck!), and the initial duplication is below it. Either way, the bottom of the stack is going to fill up with a lot of garbage, which should be inconsequential unless our sequences become absurdly long. At this point, the top of our stack is the result of the current step, so we
duplicate it, and run
x if it’s greater than 0. Finally, we
duplicate our main macro,
save it as
x, and then e
xecute the copy we left on the stack.
Code golf is a quirky little game – complete the challenge at hand in your preferred programming language in as few bytes as possible. Whole languages exist just for this purpose, with single-character commands and little regard for whitespace.
dc always seemed like a plausible language for these exercises, and I recently attempted a few tasks which I would not ordinarily use dc for, notably 99 Bottles of Beer.
This is an old post from an old blog; assets may be missing, links may be broken, and my opinions may differ considerably by this point…
Even though I generally have an HP or two handy, the POSIX command-line RPN calculator, dc, is probably the calculator that I use most often. The manpage is pretty clear on its operation, albeit in a very manpagish way. While the manpage makes for a nice reference, I've not seen a friendly, readable primer available on the program before. This is likely because there aren't really people lining up to use dc, but there are a couple of compelling reasons to get to know it.