The Tiny RPN Calculator: Version 1.06 Overview

Extending the Calculator

For Version 1.06 the the chief aim is to enable the calculator to store the argument (or arguments) of the most recent calculation so that the user can recall them when necessary and push them onto the stack. Version 1.06 implements two new stack functions: la ("Last Argument(s)"), and dr ("Drop"), the latter of which just pops the top element of the stack. In certain circumstances use of both functions can eliminate much of the pressure of assigning to variables in a program, and thus minimize the risks of wrongly overwriting previously established variables.

Creating the capacity to store and recall last arguments seems straightforward on its face, but is actually fairly complicated, because (1) some math functions are unary (i.e., they take only one argument) while others are binary (these take two); and (2) there are five operators and 32 math functions through which we must distribute the effect of recording the argument (or arguments) of the most recent computation. The challenge is to find an economical way to achieve this distribution. It all begins with encapsulating the arguments and their associated functions within a brand new class called Arguments. This is a simple class that contains private fields of type double, called value1 and value2, to hold either or both arguments of the most recent math operation or function; and another field of type int called arity, which behaves like a flag, signalling whether the most recent operation took one or two arguments. The Arguments class contains two public methods that effect storage and retrieval of the argument(s) respectively. An instance of Arguments, called lastArguments, is made a member of the Calculator class. When the method Calculator::DeployStackFunction() encounters the la command, it calls the method Arguments:: Restore(), which first monitors arity to determine whether to push onto the stack either value1 alone, or both of value1 and value2. So far, simple enough.

The complexity arises when we begin to consider recording of the last argument, or arguments. The method Arguments:: Record() sets the flag arity to either 1 or 2 before popping, recording, and replacing one or two arguments as appropriate. Now we must find a way to minimize the number of calls to this function. Fortunately, the math operators +, -, *, /, and % are all binary, so in the Calculator::DeployMathOperator() method, only one call to Arguments::Record() is required. (We simply have to avert the possibility of invoking this method when the operation is assignment instead.) But the method Calculator::DeployMathFunction() is another story altogether, because many of these functions are unary, whereas some are binary. That is the main reason why we scuttled the lengthy if-else block in the DeployMathFunction() method back in Version 1.02, and went with a lengthy switch statement instead. Had we stayed with the if-else, we would have had to insert a call to Arguments::Record() into the code block of each and every math function. That's 32 instances of a single function call! Much too redundant, much too messy, and quite unnecessary. Instead, we merely group these functions according to whether they take one or two arguments. Then we insert some simple code at the top, which determines the value of arity based on the index number of the math function being invoked. In so doing we need make only one call to Arguments::Record(). The switch statement thus turns out to be quite an elegant solution for storing and retrieving last argument(s).

Using the New Stack Functions

Let's put the new functions la and dr to work. Suppose we're tasked with writing a program called f1 to evaluate the following function:  f(x, y) = (x + ln y / y) / ln x . Suppose also, for the sake of argument (no pun intended!), that we don't want to risk writing over the values stored in the variables X and Y. We could choose other variables with which to write the program, assuming they are available. However, there is another solution that is superior, because it avoids variable assignment completely. That solution is shown immediately below:

pgm
Enter a name for the program: f1
Enter a sequence of program instructions; terminate by entering "end": dp ln sw / + la dr ln / ? end
   Program "f1" created.
   Save "f1" to file? (y/n) y
   Program "f1" saved to file "f1.pf".
47 95 f1
   12.219774516
921.3 77.88 f1
   134.981665094

Let's trace this chain of calculations to determine what happens at its most crucial steps. It should be clear that the sequence of instructions dp ln sw / + goes as far as to calculate the numerator of the function f1:  (x + ln y / y) . But once these terms are added together, they are no longer available on the stack, and we need x to compute the function's denominator. So we restore the two arguments by way of the la command and push them back onto the stack. (Both arguments are restored because the operator + is binary.) Since we no longer need the argument  ln y / y , we just pop it off the stack using the dr command. This leaves x at the top of the stack, whereupon we take its natural logarithm, divide numerator by denominator, and output the result.

The Tiny Calculator's Limitations; Looking Ahead...to Version 2.00?

Anyone who has used an HP 48G calculator, or the newer HP 50G model, will recognize at once that the Tiny RPN Calculator is no match for either of them. Nor was it intended to be. No C++ program spanning just 700 lines or so of code could accomplish what these calculators accomplish. The Tiny Calculator does not display its stack, it does not have graphic capability, it cannot be programmed with branching statements and loops, and for that matter, it only works within the real number system. What about other mathematical objects such as complex numbers, vectors, matrices, algebraics, ordered tuples, sets, and the like? Our calculator also falls far short in terms of the stack functions available to it. The HP calculators include at least a dozen more functions that enable users to deal with the stack interactively. (Chances are good that all of these functions could be implemented in our own calculator by applying programming acumen and some hard thinking.) And even if you have interesting applications that are restricted to computation within the real number system, it's a good bet that trying to implement some of them on the Tiny Calculator will be cumbersome at best, and futile at worst. In other words, if you desire a certain application for which the Tiny Calculator proves unsuitable, write it.

All that said, adding storage and recall of last arguments does point the way toward possible future development of the Tiny Calculator, if someone cared to develop it. A Version 2.00 of this calculator, if it ever came into being, should deal not just with real numbers but with all sorts of mathematical objects. These objects would very likely be descendants of a generic MathObject class, and the stack would be designed to hold elements of type MathObject. This is the real reason why recalling last arguments proves to be such a powerful and useful facility. It's difficult enough to re-enter a floating-point number; imagine trying to re-enter a 9 x 9 matrix of floating-point numbers, or some such!

Exercises

1) Using stack functions, write and save to file a program called delta%2 that functions exactly like delta% of Version 1.05, but without assigning to variables. Your solution should contain no more than nine program instructions if it does not use strings to label outputs; somewhat more than nine if strings are used.

2) Using stack functions, write and save to file a program called my%ch2 that functions exactly like the built-in function %ch, but without the variable assignments that complicate my%ch of Version 1.05. If you do this correctly, your finished program will contain no more than eight instructions.

3) You're already aware of the pr function of the calculator—the function that adjusts precision of output to a certain specified number of digits. Many times, however, when outputting decimal numbers we would like to express the fractional part of the number using a specific number of digits irrespective of the magnitude of the number's integral part. (Think here of formatting numbers in dollars-and-cents, such as we did in Version 1.01, Exercise 3—i.e., with just two digits after the decimal point.) In cases like these the pr function cannot help. Let us therefore add to our library of useful functions the function fmt. This function will take as arguments the decimal number to be formatted, and the number of desired digits to the right of the decimal point, in that order; the function will convert our decimal number accordingly. Write and save to file a program called fmt that accomplishes this task (at least for any number of digits that falls reasonably within our predefined scope of precision). Your program should use only stack functions; no assigning to variables. Test your program against some well-known decimal constants. (The fmt function is analogous to the FIX facility found in HP calculators; however, for our function the name "fmt" is preferred because it operates on only one number at a time, whereas HP's FIX facility fixes all outputs to the desired number of decimal places until the user de-programs it, or re-programs it.)

4) Think of your own applications that would be worthy of programming on the Tiny Calculator, and saving to files for future use; then implement them.

Solutions to Exercises

1) We present two solutions below. The first is a "bare-bones" implementation that does not output strings; the second employs strings to function exactly as does delta% of Version 1.05. (Credit for this solution belongs to the Hewlett-Packard Corporation; it's one installment of their HP 48 One Minute Marvel series.)

pgm
Enter a name for the program: delta%2
Enter a sequence of program instructions; terminate by entering "end": %ch la sw %ch min ? la max ? end
   Program "delta%2" created.
   Save "delta%2" to file? (y/n) n
8 10 delta%2
   -20
   25
10 8 delta%2
   -20
   25
320.2 3.202 delta%2
   -99
   9900
6.77 286.5 delta%2
   -97.6369982548
   4131.90546529
pi e sq delta%2
   -57.4831668412
   135.200960586

pgm
Enter a name for the program: delta%2
   The program "delta%2" already exists. Do you want to overwrite it? (y/n) y
Enter a sequence of program instructions; terminate by entering "end": 
%ch la sw %ch min $ 'percent' 'decrease' nl la max $ 'percent' 'increase' nl end
   Program "delta%2" created.
   Save "delta%2" to file? (y/n) y
8 10 delta%2
   -20 percent decrease
   25 percent increase
10 8 delta%2
   -20 percent decrease
   25 percent increase
320.2 3.202 delta%2
   -99 percent decrease
   9900 percent increase
6.77 286.5 delta%2
   -97.6369982548 percent decrease
   4131.90546529 percent increase
pi e sq delta%2
   -57.4831668412 percent decrease
   135.200960586 percent increase
 

2) The solution is as follows:

pgm
Enter a name for the program: my%ch2
Enter a sequence of program instructions; terminate by entering "end": sw - la sw dr / 100 * end
   Program "my%ch2" created.
   Save "my%ch2" to file? (y/n) y
   Program "my%ch2" saved to file "my%ch2.pf".
64 92 my%ch2 ?
   43.75
64 92 %ch ?
   43.75
92 64 my%ch2 ?
   -30.4347826087
92 64 %ch ?
   -30.4347826087
pi cb 36 7 / exp my%ch2 ?
   452.159896165
pi cb 36 7 / exp %ch ?
   452.159896165
36 7 / exp pi cb my%ch2 ?
   -81.8893040414
36 7 / exp pi cb %ch ?
   -81.8893040414

3) Note that in this solution, and in some solutions to previous exercises, we have declined to build the output instruction ? into our program. The reason for that is to give users the option to print outputs with or without strings to label them.

pgm
Enter a name for the program: fmt
Enter a sequence of program instructions; terminate by entering "end": 10 sw yx * la sw dr sw round sw / end
   Program "fmt" created.
   Save "fmt" to file? (y/n) y
   Program "fmt" saved to file "fmt.pf".
pi 2 fmt ?
   3.14
pi 8 fmt ?
   3.14159265
e 6 fmt ?
   2.718282
2 sqrt 5 fmt ?
   1.41421
2 sqrt 7 fmt ?
   1.4142136
200000000 sqrt 3 fmt ?
   14142.136
5 sqrt 1 + 2 / dp 5 fmt ? sw r 5 fmt ?
   1.61803
   0.61803
300000 7 / 2 fmt $ 'payable' 'on' 'demand' nl
   42857.14 payable on demand