The Tiny RPN Calculator: Version 1.00 Overview

Analysis of Program Design

The first step in recasting the C program of Kernighan and Ritchie into a more object-oriented implementation in C++ was to determine conceptually the sorts of things that play important parts in the functioning of an RPN calculator. It was obvious that there should be a class with which to instantiate a Stack object. It seemed equally intuitive that there should be a class to represent the calculator as a whole, and that the Calculator class should contain an object of the Stack type as a class member. This is how Version 1.00 of the program gets its start. Defining the member fields and methods of the Stack class was easy; they mirror quite closely the external variables and functions devised in The C Programming Language, except that now all the stack internals are encapsulated in the class, with no external variables or methods.

Defining the Calculator class was more difficult, but also more liberating, since there was now plenty of latitude to devise a fresh design. My habit is to begin at a very high level of code, by making the main() method the only method external to any class, and by making it only three lines long. Here is everything that main() does: (1) It declares an object of type Calculator; (2) It sends a message to the object to "run"; (3) It closes by returning the integer value 0, signifying success. These are the only three things the main() method needs to do. This three-line method persists as the only external function through every successive draft of the program; every other function is a class member.

One of my chief design goals was to break up the lengthy functions of the C code into smaller chunks ("Do one thing, and do it well" is my working maxim here), and permit longer functions only when it seems the logical thing to do. (In that regard the only lengthy function of any consequence is the one named "Calculator::DeployMathFunction()". But there it makes sense not to break up the function, as it consists merely of a long if-then-else block dealing with all and only math functions, and is therefore not hard to follow.) Thus the public method Run() of the Calculator class contains fewer lines of code than does any of the main() methods presented in The C Answer Book, albeit at a higher level of generality. What the Run() method accomplishes should be clear: it loops through all the instructions that the user has entered on the command line before pressing the <Enter> key, and tests them one-by-one to determine whether they are numeric entries, or variable names, or operator names, or names of stack functions, or names of math functions. If the entry is numeric, or the name of a variable, the Run() method pushes the value of the numeric or the variable onto the stack. If the entry is an operator or function name, Run() will deploy the operator or function as needed. The Run() method will continue to execute instructions entered by the user until the user exits the program by pressing <Ctrl><D> (Linux and Unix systems) or <Ctrl><Z> (Windows systems).

Another design goal was to do away completely with all the functions handling character-by-character input of C-style strings. The Run() method accomplishes this by incorporating this simple loop instruction:

while (cin >> entry) {
   .
   .
   .
}

Note first the variable named entry, a private member variable of the Calculator class. This important variable, used throughout the program, is of type string, which is defined in the C++ standard library. The name cin denotes the standard input stream, also defined in the standard library. The symbol >> is the extraction operator; its operands are the input stream cin and the string entry. The instruction "cin >> entry" can be defined to function as follows: "Read characters from standard input (the keyboard), and as soon as a whitespace character is encountered, store these characters in the string named entry." In future drafts of this program we will generalize this instruction to include input from other stream types, especially files; however, like cin, these will be descendants of the parent class stream, and in that respect will work quite similarly to what we have at present. So much, then, for the functions getch(), ungetch(), getline(), and getop() from the old C paradigm.

Using the Calculator: Assigning to Variables

At this point I take it that you've built Version 1.00, and have tried the examples outlined in the Project Overview. Let's look at some more examples, and at the same time explore some of the Tiny Calculator's features. Recall the cylinder with radius 2.4 and height 3.5, whose volume we computed previously:

2.4 sq pi * 3.5 * ?
   63.3345078964

Notice that the calculator defaults to a precision of 12 places. What if we don't need quite so much precision...or maybe we need just a bit more? Fortunately, we can adjust the precision on the fly. Let's adjust it downward to six places as follows: Type "6", followed by the command pr, followed by ? as shown below (don't forget to press <Enter> at the end of the sequence!):

2.4 sq pi * 3.5 * ?
   63.3345078964
6 pr ?
   63.3345

The calculator allows a maximum precision of 16 places. We invoke the maximum as below:

2.4 sq pi * 3.5 * ?
   63.3345078964
6 pr ?
   63.3345
16 pr ?
   63.33450789637023

For the time being, let's adjust the precision back to 12 places. Now we'll attempt to calculate the surface area of this cylinder. When you think this problem through, you'll see that at some point we'll need to invoke the radius twice: once to compute the composite area of the bases, and again to determine the area around the circumference. One way to do this (though not the only way by any means, as we'll also see shortly) is to assign the values of the radius and height to variables that are implemented by the calculator. The Tiny Calculator enables storage of up to 26 real-number values in the variables labeled "A" through "Z". Let's make use of the variables R and H as we calculate the cylinder's surface area:

2.4 R = 3.5 H = 2 pi R sq * * 2 pi R H * * * + ?
   88.9699039497

Observe that the equals sign = has been employed by the calculator as an assignment operator, not as a symbol denoting equality. Observe also that this operator is always postfixed to the variable assigned to, which in turn follows the value to be assigned—and in exactly that order. (If you're used to algebraic notation, you may find this a bit counterintuitive. You may prefer to think that assignment of 2.4 to R should read "R = 2.4". But remember, this is RPN, not algebraic notation! Nor will it do to postfix = as follows: "R 2.4 =". Both of the latter attempts at assignment will yield errors of some sort, as you'll see when we get to Exercise 3 below. As long as you think "(1) value first; (2) variable second; (3) assignment operator third," you'll be fine.)

Assigning to variables will be particularly useful whenever you invoke certain quantities numerous times in the course of a calculation. To illustrate, we will calculate the hyperbolic cosine (or "hypercosine") of 2.2—but we're going to do it twice: first, "the easy way" (using the cosh function), then "the hard way," by invoking the formula  cosh x = (ex + e-x) / 2 :

2.2 X = X cosh ? X exp X chs exp + 2 / ?
   4.5679083289
   4.5679083289

Now let's be intrepid, and attempt to chain calculations together as follows: let x = 3.71, then calculate the hypercosine of x "the hard way." We'll also calculate the hypersine of x "the hard way," using the formula  sinh x = (ex - e-x) / 2 . At the same time we're doing all that, we will verify the following formula for hyperbolic functions:  cosh2 x - sinh2 x = 1 (at least we'll verify it for x = 3.71). To accomplish this as efficiently as possible, we will exploit certain properties of the stack as we manipulate it. Here is one way of going about it all:

3.71 X =
X exp X chs exp + 2 / ? sq ?
   20.4391420251
   417.758526723
X exp X chs exp - 2 / ? sq ?
   20.4146645019
   416.758526723
- ?
   1

What exactly is going on here? First we assigned a value to X; then we used the <Enter> key to skip to the next line (for the sake of nicety only; there is no technical reason why we couldn't have written the entire series of instructions on one line). We computed cosh x "the hard way," then summoned ? to display the result; after which we immediately squared this result, then summoned ? once again to display the square. We reiterated (almost) the same sequence of instructions to yield sinh x and its square. But observe in particular that all we need to do to get the difference of the squares is to postfix the subtraction operator, then another occurrence of ?. That's because, immediately before subtraction takes place, the two topmost values on the stack are the two squares 416.758526723 and 417.758526723 respectively! After the subtraction is executed, the two squares have vanished from the stack, and their difference, equal to 1, has assumed the topmost position.

Using Stack Functions

Suppose for the moment that someone puts to you this challenge: Compute the surface area of the cylinder as in the example above, except do not store any values in variables. Can it be done? Yes! For that matter, this sort of problem lends itself quite well to the technique of manipulating the stack rather than storing values in variables. After all, the cylinder's radius is used only twice; the height, only once. Perhaps in this instance using variable memory is unnecessary, or undesirable—or perhaps many other variables are already being put to better use, and variable memory now comes at a premium.

We're already familiar with one stack function: it's the operator ?, which we have used numerous times to print results to the screen. But why is ? classified as a stack function? If you go to Version 1.00's source code and examine the method Calculator::DeployStackFunction(), you'll see that ? is the first of four functions deployed by this method. The command ? instructs the calculator to pop the topmost value of the stack, assign this value to a variable named "arg1", then push the value back onto the stack by way of arg1. Then arg1 is used to output this value to the screen. Another function, cl, is used to clear the stack of all values that remain on it.

The remaining two functions, dp and sw, hold real power for users where complex calculation is concerned, and are very often used together in the same calculation. The function dp manipulates the stack by duplicating its topmost value, so that there are actually two copies of the same value occupying the two topmost positions on the stack. The function sw swaps the two topmost values, so that what once occupied the top position is now second from the top, and vice versa. Let's apply both of these stack functions to our calculation of the cylinder's surface area, without resorting to variable storage:

2.4 dp sq pi 2 * * sw 2 pi 3.5 * * * + ?
   88.9699039497

Let's trace the course of this calculation as it unfolded. First, we pushed the value of the radius onto the stack, and immediately pushed a copy of that value onto the stack as well. We squared the copy, then multiplied it by pi and by 2 to yield the composite area of the cylinder's bases. Then we swapped the value of the composite with the original value of the radius so that we could work with this radius once again. We multiplied the radius by 2 and by pi and by 3.5, the height of the cylinder, to yield the area around the cylinder's circumference. Finally, we added the circumferential area to the base areas to yield the total surface area.

Pretty cool, wouldn't you say?

But wait—there's more. We're going to try reconstructing the entire series of calculations whereby we find the hypercosine and hypersine of 3.71 "the hard way," and at the same time verify a hyperbolic identity formula for the value 3.71. But we're not going to use variables; only stack functions. Here's how it's done:

3.71 dp dp exp sw chs exp + 2 / ? sq ?
   20.4391420251
   417.758526723
sw dp exp sw chs exp - 2 / ? sq ?
   20.4146645019
   416.758526723
- ?
   1

Can you trace the course of this calculation for yourself? If you cannot, here's the gist of what happened: First, we copied the value 3.71 twice for a total of three copies occupying the three topmost positions on the stack. This move alone is key to executing the entire chain of calculations. We took the first available copy of 3.71, raised e to that power, then swapped to get the next available copy. We changed the sign of that copy so as to raise e to -3.71. We added these powers together, divided by 2, printed the result, squared the result, and printed the square—which, by the way, now occupies the topmost position on the stack. So what occupies the next position down? You guessed it: it's the original value 3.71 that we typed on the command line. So we swapped to get at that value, and made a copy. Calculating the hypersine, and its square, follows pretty much in the same way as calculating the hypercosine and its square. The rest of the calculation is exactly the same as when we invoked the variable X instead.

Naturally, there exist many instances in which using variables provides the easiest way to keep track of the proceedings. And granted, it cannot be ignored that the use of variables parallels very closely the writing and invoking of formulas, particularly formulas that are canonized in mathematics and other sciences. In such cases using variables may prove not only desirable, but well-advised. But in many other instances, using stack functions provides a very handy alternative.

One More Example: Formatting Output

Our final example will be of a more practical bent, and consists of a simple piece of automotive engineering (or any other engineering that involves internal-combustion engines). So-called "car nuts" (many motorcycle nuts as well) will recognize at once the formula for calculating engine displacement. Engine displacement is really just a measure of an engine's working volume; accordingly, it is expressed in volumetric units such as cubic inches, or cubic centimeters ("cc."). Given the diameter of each cylinder, called the bore of the cylinder, and the stroke (the length of movement of the piston inside the cylinder), and the total number of cylinders, that formula is as follows:

Engine displacement = π * (bore / 2)2 * stroke * number of cylinders

More accurately, the above formula is a measure of raw displacement, since it assumes that the linear units used to measure bore and stroke are the same units that, in volumetric terms, express the engine's displacement. In other words, if the bore and stroke are measured in inches, and the displacement in cubic inches, then displacement proper will be the same as raw displacement.

Let's experiment with this concept by calculating the raw displacement of the classic Chevrolet 409 engine from the early 1960s. That engine has eight cylinders, with a bore measuring 4.3125 inches and a stroke of 3.5 inches. For the time being we will use the variables B, S, and C to represent bore, stroke, and number of cylinders respectively:

4.3125 B = 3.5 S = 8 C =
pi B 2 / sq * S * C * ?
   408.983821743

We note immediately that the displacement comes very close to the magic number "409" after which the engine is named. But all the trailing digits don't seem to be particularly useful or informative. We could adjust our precision downward using the pr command, but since we don't know the displacement in advance, we can't really determine whether we'll get two, three, or four trailing digits—or some other number. If we want to fix the number of trailing digits, we'll have to devise some other method. Let's exercise ourselves a bit, and do the following: We'll rework this calculation so that it prints two results to the screen. The first result will express displacement in whole cubic inches and thousandths of cubic inches, rounded to the nearest thousandth—in other words, there will be three trailing digits. The second result will be the one everyone knows and loves, namely, the one that merely rounds to the nearest whole cubic inch. Note in particular that there's no need to do the calculation over again; remember that our raw displacement is already stored at the top of the stack. It remains for us only to add some instructions to format this number to our liking, as shown below:

dp 1000 * round 1000 / ? sw round ?
   408.984
   409

So far, so good (excellent, for that matter!). However, as has already been intimated, there will be occasions on which an engine's claimed displacement is not quite the same as its raw displacement. The accepted conventions for measuring displacement in metric units are notoriously peculiar in that regard. That's because whereas an engine's displacement is most often expressed in cubic centimeters, or liters, its bore and stroke will be specified in millimeters instead. Clearly, some conversion of units will have to be undertaken somewhere in the calculation. I leave this difficulty for you to solve on your own, when we come to the set of exercises below.

Looking Ahead

By this point, it's hoped that you're having tons of fun with this calculator—so much fun, in fact, that you find yourself testing a few calculations repeatedly. Anyone with a modicum of intellectual curiosity will recognize that if we wish to confirm a trigonometric identity for a variety of values of a specified angle (or angles)—or if we wish to play around with the formula for calculating engine displacement—we will be invoking these formulas again and again. However, if we find ourselves typing the same sequence of instructions over and over, the "fun quotient" will deteriorate fairly rapidly! You may not trust my authority on most matters, nor should you, really. But trust me when I tell you that once you experiment with the Tiny RPN Calculator to any extent, you'll agree that when it comes to enhancing its abilities, our most urgent priority will be to make it programmable. That is the problem that Version 1.01 of our program is designed to address.

Exercises

1) If you have visited Hewlett-Packard's webpage on Reverse Polish Notation, you've noticed that it implements a pretty nifty online RPN calculator for you to experiment with. The page also provides four sample expressions written algebraically, then gives a suggested sequence of keystrokes for evaluating each expression using this online calculator. Using these suggestions as a guide, work out these four solutions on the command line instead, using your Tiny RPN Calculator. Do you notice any dissimilarities between the two approaches? If so, what are they?

2) Do a little research to determine the formula for calculating the proportions of the so-called Golden Rectangle. This is the rectangle that has a height of one unit and a width of approximately 1.618 units. Write a short sequence of instructions to calculate this ratio with a precision of 14 digits; then take its reciprocal, and confirm that the difference between the two amounts to exactly 1.

3) Suppose we attempt to assign a quantity to a variable while disregarding the proper syntax for the assignment operation specified above. Let's try it, and see what happens. First, assign 71 to the variable A, 72 to B, and clear the stack, by entering the sequence  71 A = 72 B = cl . Next, enter the sequence  A = 4 A ? . Lastly, enter  B 4 = B ? . Using the Version 1.00 source code as your guide, try to explain each of the two outputs in terms of what you see in the code.

4) Upon inspection of the source code, you should have noticed that the method Calculator::DeployMathFunction() provides for three hyperbolic functions: sinh, cosh, and tanh. Write a short sequence of instructions to compute the hyperbolic cosecant, the hyperbolic secant, and the hyperbolic cotangent of an arbitrary value assigned to the variable A.

5) Without using variables, write a sequence of instructions to calculate the raw displacement of an engine by first specifying its bore, stroke, and number of cylinders in exactly that order. Use as your example the Ford 427 racing engine of 1963, which has a bore of 4.23 inches, a stroke of 3.78 inches, and eight cylinders. Your calculation should present two results: the first rounded to the nearest thousandth of a cubic inch, the second rounded to the nearest cubic inch.

6) This is a two-part exercise involving calculation of engine displacement in metric units. For each exercise, store the bore, stroke, and number of cylinders in the variables B, S, and C, and once raw displacement is determined, store that in D before formatting your answers: (a) Determine the displacement of a 1970 Yamaha R5 motorcycle, which has two cylinders, a bore of 64 mm., and a stroke of 54 mm. Present two answers, both in cc. (cubic centimeters). The first should be rounded to the nearest cubic millimeter; the second, to the nearest cubic centimeter. Then (b) compute the displacement of the Aston Martin One-77 supercar, with bore of 94.0 mm., stroke 87.8 mm., and twelve cylinders. Present three formats here; the first two should be specified as for the Yamaha motorcycle above, while the third (which is commonly employed in automotive parlance) expresses displacement in liters, rounded to the nearest tenth of a liter.

7) Using a reference work on mathematics, select a formula expressing one of the trigonometric identities. Verify this identity for one or more sets of angular values, which you've assigned to the variables A and B as necessary. (Remember that the calculator's trigonometric functions take angular values to be expressed in radians, not degrees.)

Solutions to Exercises

1) The four expressions are  1 + 1;  (3 + 5) / (7 + 6);  1 / (1/30 + 1/40);  and (5 + 6 * 7) * (4 + 3) / (1 + (5 + 6 * 7)). Their evaluations on the command line proceed as shown below:

1 1 + ?
   2
3 5 + 7 6 + / ?
   0.615384615385
30 r 40 r + r ?
   17.1428571429
5 6 7 * + dp 4 3 + * sw 1 + / ?
   6.85416666667

There are dissimilarities between the two approaches, some minor, others of more consequence. One of the minor differences concerns choice of notation. To make commands a bit easier to type on the command line, the Tiny Calculator strives for a certain concision, opting for r in place of "1/x", and sw in place of "L1<>L2". But the bigger difference is that on the command line you don't have to press <Enter> every time you want to push a numeric value onto the stack; just pressing the <Enter> key at the end of the sequence will instigate all the stack operations in their proper order. However, this advantage comes at a price: On the command line, you cannot easily visualize what's going on inside the stack, whereas most RPN calculators such as those manufactured by HP include provisions for viewing at least the topmost four levels of the stack in action.

Both approaches are valid; both have their strengths and limitations. It might be argued, however, that the importance of visualizing stack operations gradually diminishes as one accumulates more experience with RPN, and acquires a greater understanding of how it works. Experienced users of hand-held RPN calculators realize this. They will have noticed that, unlike their own calculators, the online calculator on HP's webpage contains no <SPC> key. This key enables users to bypass using the <Enter> key over and over, by entering spaces between numeric inputs and commands—just as though they are calculating on the command line, which they are!

2) The "Golden Ratio" can be evaluated using the expression (5½ + 1) / 2 . The sequence of RPN instructions called for here, and their results, are

5 sqrt 1 + 2 / 14 pr ? dp r ? - ?
   1.6180339887499
   0.61803398874989
   1

3) The inputs and outputs of our mildly misbegotten attempts at assigning to variables are as follows:

71 A = 72 B = cl
A = 4 A ?
   ERROR: Stack empty.
   0
B 4 = B ?
   ERROR: No variable being assigned to.
   72

Here's what happened in the first instance. The instruction cl summoned the calculator to clear the stack, via the method Calculator::DeployStackFunction(). The instruction A pushed the value of A, namely 71, onto the top of the stack, thanks to the Calculator::Run() and Calculator::PushVariableValue() methods. But when we come to the instruction = , the method Calculator::DeployOperator() sends a message to the stack to pop the value of A from the top, thus emptying the stack once more. When we inspect the Stack::Pop() method, we find that once the stack has been emptied, the method relays the string "ERROR: Stack empty." to the output stream cout, then returns the value 0.0. But according to proper syntax for assignment, 0.0 is then assigned to A! After that the number 4 and the value of A are pushed onto the stack, the number 4 having had no effect whatsoever on the assignment to A. The instruction ? reveals that the value of A is 0, not 4. (Note that the "ERROR: Stack empty." message is still output to the screen notwithstanding that the stack now contains the value 4 at its second level, and the value 0 at the top. That's because once the error message is sent to the output stream cout, it cannot be retrieved. Thus what the message really tells us is that at some point in the course of the computation, the stack had been emptied—not necessarily that it's currently empty.) On the other hand, if we had invoked the commands  A = 4 A ? somewhere in the midst of a calculator session, when the stack isn't empty, we would not have gotten the error message, but the instructions A and = would have inadvertently assigned to A whatever was at the top of the stack at the time—not necessarily the value 4!

In the second instance, the value of B (which is 72) and the number 4 are immediately pushed onto the stack. Once the instruction = is encountered, the Calculator::DeployOperator() method pops the number 4 from the top. Since the value of B now occupies the top, there is no "ERROR: Stack empty." message. Now, however, when this method inspects the value of the string lastEntry, it finds the value to be the numeric literal "4", and not the name of a variable (such as "B"). At that point this method relays the message "ERROR: No variable being assigned to." to the output stream cout. Lastly, the instructions B and ? reveal the value of B to be 72, not 4, which is what we should have come to expect by this point.

4) This is easily achieved by taking the reciprocals of the functions provided for:

2.12 A = A sinh r ? A cosh r ? A tanh r ?
   0.243572550921
   0.236653647856
   1.02923640995

5) The solution is as follows:

4.23 3.78 8 * sw 2 / sq pi * * dp 1000 * round 1000 / ? sw round ?
   424.964
   425

(Historical note: It seems odd to have named an engine "427" when it actually displaces 425 cubic inches. However, the Ford Motor Co. chose the name "427" because 427 cubic inches is the rough English equivalent of 7.0 liters, which at that time was the maximum displacement allowed in NASCAR racing.)

6) There is more than one way to handle the unit conversions involved, but perhaps the most straightforward and easy-to-understand method would be to convert bore and stroke from millimeters to centimeters as soon as you encounter them. Below are the solutions for the Yamaha and Aston Martin, respectively:

64 B = 54 S = 2 C =
pi B 10 / 2 / sq * S 10 / * C * D =
D 1000 * round 1000 / ? D round ?
   347.435
   347
94 B = 87.8 S = 12 C =
pi B 10 / 2 / sq * S 10 / * C * D =
D 1000 * round 1000 / ? D round ? D 100 / round 10 / ?
   7311.75
   7312
   7.3

7) We have dozens of such formulas to choose from, but for the sake of example we'll work with these two:

tan 2A = 2 * tan A / (1 - tan2 A)

sin A - sin B = 2 * cos ((A + B) / 2) * sin ((A - B) / 2)

2.71 A = 4.06 B =
A 2 * tan ? 2 A tan dp sq 1 sw - / * ?
   -1.16906663415
   -1.16906663415
A sin B sin - ? 2 A B + 2 / cos A B - 2 / sin * * ?
   1.21295369043
   1.21295369043