RPN Calculator User Manual

Introduction

Anyone who uses the Python interactive command line knows that Python is a good interactive calculator. However, there are many people who prefer Reverse Polish Notation (RPN) for simple interactive calculations. The rpncalc module provides an interpreter for RPN that easily integrates with the standard Python interpreter.

Features

References

Tutorial

This tutorial presents the concepts needed to operate the RPN interpreter. Extending the interpreter is covered in the Programming section. Before you get started, you need to install the interpreter. Follow the instructions in the Installation section.

There is no attempt to teach you how the Python interactive interpreter works. If you are new to interactive use of Python, you should start with the Python tutorial which is included in the standard Python documentation package.

You will want to start with the rpn.py script from the examples directory. This script must be run with the -i switch on the Python command line. If this is not done, a number of interactive features don't work. In particular, on my Debian box, the command history doesn't work. The following command will work on most systems if rpn.py is in the current directory.

$ python -i rpn.py

Getting Help

You can use the help command or its alias ? to display on-line help for any command. As an example, display the help on the add command.

rp> help +
+ (a b -- c)
    c = a+b

This displays the name of the command, the stack picture, and a description of what the command does. The stack picture shows how the stack is altered by the command. There can be other entries on the stack which are not pictured. The operation has no effect on these entries. In the above example, the stack picture shows that the add operation takes two values from the stack and returns the sum.

Some commands take input from the input stream as well as the stack. For these cases, the stack picture has a different form as can be seen in the following example.

rp> ? solve
solve (f (x1 x2 -- x)
    Solve the equation f(x) = 0.  The parameters x1 and x2 must bound the
    solution.  The function f(x) must take a single real input and produce a
    single real output.

This example shows that the solve command takes two inputs from the stack and the name of the function from the input stream. If f is the name of a function that computes x*x-2 the following will compute the square root of 2.

rp> 1 2 solve f

Basic Operations

The simplest way to learn to use the RPN interpreter is to see several examples side by side with the equivalent calculation done using the Python interpreter. In the following, you can tell which interpreter is operating by the prompt.

>>> print 1+2
3
rp> 1 2 + .
3
>>> print mpq(2+3)/(5+7)
5/12
rp> 2 3 + 5 7 + / .
5/12
>>> print mpq(1,3)+mpq(1,5)
8/15
rp> 1/3 1/5 + .
8/15
>>> print 1+1j + 1-1j
(2+0j)
rp> 1+1j 1-1j + .
(2.0+0.0j)

From the above examples, we can make several observations.

Now lets see a couple of math operations in action. Note that I prefer the ln name to log so it is provided as an alias.

>>> print sqrt(2)
1.4142135623730950488
rp> 2 sqrt .
1.41421356237
>>> print log(2)
0.69314718055994530943
rp> 2 log .
0.69314718056
rp> 2 ln . # Alternate name for log
0.69314718056

For those needing complex numbers, the same functions can be applied to complex inputs. This differs from the Python standard library which has a separate module to handle complex numbers.

>>> print sqrt(-2+0j)
(0.0+1.4142135623730950488j)
rp> -2+0j sqrt .
(0.0+1.41421356237j)
>>> print log(2j)
(0.69314718055994530943+1.5707963267948966193j)
rp> 2j log .
(0.69314718056+1.57079632679j)
rp> 2j ln . # Alternate name for log
(0.69314718056+1.57079632679j)

The following examples are rather obvious and the Python versions are verbose (because the Python interpreter does not have a built-in rational type), so only the RPN form is presented.

rp> 1/2 1/3 sum .
5/6
rp> 1/2 1/3 prod .
1/6
rp> 1/2 1/3 1/5 1/7 1/11 1/13 sum .
40361/30030
rp> 1/2 1/3 1/5 1/7 1/11 1/13 prod .
1/30030

Base conversions are demonstrated in the following example.

rp> 0xff .  # Hex input
255
rp> 256 .x  # Hex output
0x100
rp> 0o377 . # Octal input
255
rp> 256 .o  # Octal output
0o400
rp> 0b101 . # Binary input
5
rp> 10 .b   # Binary output
0b1010

Stack Operations

The stack is the central data structure of all RPN style calculators. Eventually, it becomes necessary to manipulate the items on the stack. This section shows the operations that are available. Note that these are all borrowed from the Forth programming language.

In order to show the stack effects, another operator is introduced (.s) which non-destructively prints the stack contents. This operator is extremely useful if you need to see how a long calculation is going.

rp> 1 dup .s # Duplicate the top of the stack.
Stack depth: 2
1
1
rp> clear .s # Clear the stack.
Stack depth: 0
rp> 1 2 .s
Stack depth: 2
2
1
rp> drop .s  # Remove the top of the stack.
Stack depth: 1
1
rp> clear
rp> 1 2 .s
Stack depth: 2
2
1
rp> swap .s  # Swap the top two elements on the stack.
Stack depth: 2
1
2
rp> clear
rp> 1 2 .s
Stack depth: 2
2
1
rp> over .s  # Make a copy of the second element on the stack.
Stack depth: 3
1
2
1
rp> clear
rp> 1 2 3 .s
Stack depth: 3
3
2
1
rp> rot .s   # Rotate the third element to the top of the stack.
Stack depth: 3
1
3
2
rp> clear

In some cases, the stack manipulation can get a lot more complex than the above operations can accommodate. There are two options that can be used to handle this problem. One is to use an alternate stack to hold values out of the way until they are needed. The other is to use named variables. This second alternative is demonstrated in the next section.

The alternate stack is another concept borrowed from the Forth programming language. (If you are familiar with Forth, you will recognize that the alternate stack is used like the return stack.) The following operators move data on and off the alternate stack.

rp> 1 >a .s  # Move the top of the main stack to the alternate stack.
Stack depth: 0
rp> a@ .     # Fetch the top of the alternate stack but don't remove it.
1
rp> a> .     # Move the top of the alternate stack to the main stack.
1

Since the stack is such a central data structure to the RPN interpreter, it is necessary to have an easy way to move data on and off the stack from the Python interpreter. Without this mechanism, it would be very clumsy to switch back and forth between interpreters. The following examples show how to switch between interpreters and move data back and forth.

rp> 1 2 3        # Put some values on the stack.
rp> py           # Switch to the Python interpreter.
>>> print pop()  # Pop one value.
3
>>> a,b = pop(2) # Pop two values and save them in variables.
>>> print a,b
1 2
>>> push(4,5,6)  # Push some values on the stack.
>>> print get(3) # Show that they got there.
[4, 5, 6]
>>> rpn()        # Switch back to the RPN interpreter.
rp> .s           # Show that the values are on the stack.
Stack depth: 3
6
5
4

Variables

Every calculator designed to support extended calculations needs some kind of memory function. In the RPN interpreter, the memory function is handled by named variables. The following examples show how these variables are created and used from both the RPN and Python interpreters.

rp> # Create three variables and show that nothing is left on the stack.
rp> 1 := a 2 := b 3 := c .s
Stack depth: 0
rp> .v  # Quick way to show the contents of all defined variables.
a = 1
b = 2
c = 3
rp> a b c sum .  # Use the variables in a calculation.
6
rp> py  # Switch to the Python interpreter.
>>> v=getv()  # Get the variables from the RPN environment.
>>> print v   # Display them.
a = 1
b = 2
c = 3
>>> v.d=4     # Create a new variable.
>>> print v
a = 1
b = 2
c = 3
d = 4
>>> setv(v)   # Update the variables in the RPN environment.
>>> rpn()
rp> d .  # Verify that the new variable transferred over.
4

Augmented assignment is a convenient way to update variables. Since the names of the operators are the same as they are in Python, a few examples should be sufficient.

rp> 1 := a 1/2 := b 0.5 := c 1+1j := d
rp> 1 += a
rp> 2 -= b
rp> 3 *= c
rp> 4 /= d
rp> .v
a = 2
b = -3/2
c = 1.5
d = (0.25+0.25j)

Variables can be deleted to eliminate clutter.

rp> .v
a = 2
b = -3/2
c = 1.5
d = (0.25+0.25j)
rp> delete_variables a b
rp> .v
c = 1.5
d = (0.25+0.25j)
rp> delete_variables c d
rp> .v


Polynomials And Rational Functions

An interesting extension to the RPN calculator is to add polynomials in a manner that allows them to be manipulated like numbers. In order to support polynomials efficiently, it is necessary to invent a notation that will allow the number conversion process to convert the notation into a polynomial object. The notation selected has the following form:

coef,exp;coef,exp;...;coef

In other words, a string of polynomial coefficient, exponent pairs. The exponents must be non-negative integers. The coefficients can be any numeric format recognized by the number converters currently installed.

rp> 1,2;2,1;1 .
x^2+2*x+1
rp> 1,1;1 dup .s
Stack depth: 2
x+1
x+1
rp> * .
x^2+2*x+1
rp> 1/2,4;-1/3,2;1/5 := p
rp> .v
p = 1/2*x^4-1/3*x^2+1/5
rp> p .r       # It is instructive to see the representation of an object.
Polynomial(mpq(1,5), mpq(0,1), mpq(-1,3), mpq(0,1), mpq(1,2))
rp> p deg .    # Degree of the polynomial.
4
rp> p deriv .  # Compute the derivative of the polynomial.
2*x^3-2/3*x
rp> p integ .  # Compute the integral of the polynomial.
1/10*x^5-1/9*x^3+1/5*x
rp> p 1 eval . # Evaluate the polynomial at the value 1.
11/30

If you need to compute the coefficients, take advantage of the fact that polynomials can be mixed with ordinary numbers in a calculation. The number is converted to a constant polynomial before the operation is performed. To illustrate this technique, compute a polynomial that is the truncated series expansion of the exponential function.

rp> 1;1,1 := p   # Start with the first two terms.
rp> 1/2 := c     # Next coefficient
rp> c 1,2 * += p
rp> 3 /= c
rp> c 1,3 * += p
rp> 4 /= c
rp> c 1,4 * += p
rp> 5 /= c
rp> c 1,5 * += p
rp> 6 /= c
rp> c 1,6 * += p
rp> 7 /= c
rp> c 1,7 * += p
rp> 8 /= c
rp> c 1,8 * += p
rp> 9 /= c
rp> c 1,9 * += p
rp> p .
1/362880*x^9+1/40320*x^8+1/5040*x^7+1/720*x^6+1/120*x^5+1/24*x^4+1/6*x^3+1/2*x^2+x+1
rp> 
rp> # Check this by evaluating it at a few points and comparing with the built-in
rp> # function.  It is tedious to keep typing eval so create a function from the
rp> # polynomial.
rp> p to_fun aexp
rp> 0 aexp .
1
rp> 0.5 aexp 0.5 exp - .
-2.81876934327e-10
rp> 1 aexp 1 exp - .
-3.02885852996e-7
rp> -1 aexp -1 exp - .
-2.52458920276e-7

Switch to the Python interpreter to check the polynomial against the built-in function for a large number of sample points.

rp> py
>>> from math import exp
>>> v = getv()
>>> err = [abs(y - exp(x)) for x,y in v.p.float().sample(-1,1,100)]
>>> print len(err)
100
>>> print max(err)
3.02885852843e-07
>>> print err[0], err[50], err[-1]
2.52458920214e-07 0.0 3.02885852843e-07

This notation is extended to support rational functions (ratio of two polynomials). The rational function is entered as "n|d" where n is the numerator polynomial and d is the denominator polynomial. The polynomials are entered using the notation described above.

rp> 1,1;-1|1,1;1 := z
rp> z .
x-1
---
x+1
rp> 1/7,7;1/5,5;1/3,3;1,1 := p
rp> p .
1/7*x^7+1/5*x^5+1/3*x^3+x
rp> # An approximation to the natural log is produced by evaluating the polynomial
rp> # using the rational function as the input.
rp> p z eval 2 * := aln
rp> aln .
352/105*x^7+112/15*x^6+112/5*x^5-112/5*x^2-112/15*x-352/105
-----------------------------------------------------------
        x^7+7*x^6+21*x^5+35*x^4+35*x^3+21*x^2+7*x+1
rp> # Check it at a couple of points.
rp> aln 1 eval .
0
rp> aln 2 eval float dup .
0.693134757332
rp> # Compare it to the built-in function.
rp> 2 ln - float .
-1.24232276571e-05

Use the Python interpreter to compare the rational function approximation with the built-in function at a large number of sample points.

rp> py
>>> v = getv()
>>> # Compute the error relative to the built-in function
>>> from math import log
>>> err = [abs(log(x) - y) for x,y in v.aln.float().sample(1,2,100)]
>>> print len(err)
100
>>> print max(err)
1.24232276572e-05
>>> print max(err[:50])
1.09272297488e-07

Programming

Programming the calculator is relatively easy. Usually you simply create an ordinary Python function and install it into the calculator using one of the wrapper functions. However, some terminology will help in explaining what is going on behind the scenes. This terminology is borrowed from the Forth programming language.

When a token is extracted from the input stream by the RPN interpreter, the first thing that it does is search a list of vocabularies for a definition. If the definition is found, the associated function is called. The called function is responsible for any stack manipulation that is required to execute the operation. A vocabulary is nothing more than an augmented Python dictionary. The dict type is sub classed and a name attribute is added so that the vocabulary name can be printed when the user asks for a listing of the contents. You can get a listing of the search order and the contents of the active vocabularies with the words command as follows.

rp> words
Vocabulary variable:
<empty>

Vocabulary user:
<empty>

Vocabulary system:
% & * ** *= + += - -= . .b .o .r .s .v .x / // /= 1/x 2drop 2dup := << >> >a ?
Hn Jn_pq Ln_s Pn Tn Un ^ a> a@ abs acos acosh asin asinh atan atan2 atanh
binomial bye ceil cis clear cmpf cmpq coef complex conjugate cos cosh def deg
degrees delete_variables denom deriv divmod doublefactorial drop dup e eval exp
factorial float floor from_roots fsave gcd get_mpf_prec help hypot imag
improve_roots int integ inv ln lnfactorial log log10 log2 logb long max min mpf
mpq neg nip numer over phase pi prefer_cmpf prefer_cmpq prod py radians ratapx
real restore roots rot round round_n save set_mpf_prec sin sinh solve sqrt
state stats sum swap tan tanh to_fun unique_roots use_degrees use_radians words
words_all |


Note that the user defined variables are in their own vocabulary and it is the first one in the search order. The system vocabulary is the last one searched so that the user can override any of the built in operations. The user vocabulary is reserved for installing user defined operators. As an example, we will install a random number function by wrapping the random function from the random module in the standard library. The Python function function in the rpncalc module is used to install Python functions for use in the calculator (see the doc string for more information).

rp> py
>>> from random import random
>>> # Install the Python function.
>>> function(random,0,name='rand')
>>> rpn()
rp> rand .
0.0946475237991
rp> rand .
0.253025585384

It can get tedious to drop into the Python interpreter to define and install simple one-liners. The RPN interpreter has a def command that simplifies the creation of simple functions. Suppose you want to use the equation solver to compute the solution to x*x-2=0. The following example shows how to enter the problem.

rp> def f(x) x*x-2
rp> 1 2 solve f
rp> dup .
1.41421356237
rp> 2 sqrt - .
-4.90723635614e-19

The def command can be used to define more complex functions as in the following. Note that white space is not allowed in the specification of the function name and the parameter list since it must resolve to a single RPN interpreter token.

rp> def g(x,y) sqrt(x*x + y*y)
rp> 1 2 g
rp> dup .
2.2360679775
rp> 1 2 hypot
rp> - .
0.0

The above example is a poor substitute for the hypot command because the hypot command will deliver more accurate results in cases where the squaring operation looses precision or causes an overflow.

If you need a Chebyshev approximation to some function, you can create one as demonstrated in the following example.

rp> py
>>> from rpncalc.chebyshev import Chebyshev as cheby
>>> pi2 = pi(20)/2
>>> sinx = cheby(sin, -pi2, pi2, 20, 20)
>>> err = [abs(y - sin(x)) for x,y in sinx.sample(-pi2,pi2,1000)]
>>> print max(err)
4.763088376965169417e-22
>>> for c in sinx.coef: print c
1.00974195868289511094e-29
1.1336481778117478755
-1.2621774483536188887e-30
-0.13807177658719210353
3.786532345060856666e-30
0.0044907142465549179127
-8.8352421384753322205e-30
-6.7701275842152491163e-5
-1.2621774483536188887e-29
5.891295330289313335e-7
-2.9030081312133234439e-29
-3.338059408918622321e-9
-4.1651855795669423325e-29
1.3297028384489703895e-11
-1.5587891487167193275e-28
-3.9274995871796995165e-14
3.786532345060856666e-29
8.9452601450820822636e-17
2.1141472259923116384e-29
-1.6213534506195630502e-19
>>> sinx = sinx.edit(1e-16)
>>> err = [abs(y - sin(x)) for x,y in sinx.sample(-pi2,pi2,1000)]
>>> print max(err)
8.960260661335075353e-17
>>> for c in sinx.coef: print c
0
1.1336481778117478755
0
-0.13807177658719210353
0
0.0044907142465549179127
0
-6.7701275842152491163e-5
0
5.891295330289313335e-7
0
-3.338059408918622321e-9
0
1.3297028384489703895e-11
0
-3.9274995871796995165e-14
>>> # Install the function.
>>> function(sinx,1,name='sinx')
>>> rpn()
rp> pi 3 / dup sinx swap sin - .
1.41837524018e-17

Another method of programming the calculator is to define a function using a string of RPN interpreter commands. The following example demonstrates this method.

rp> py
>>> prog('log2','ln 2 ln /')
>>> rpn()
rp> 32 log2 .
5.0
rp> 256 log2 .
8.0
rp> words
Vocabulary variable:
<empty>

Vocabulary user:
f g log2 rand sinx

Vocabulary system:
% & * ** *= + += - -= . .b .o .r .s .v .x / // /= 1/x 2drop 2dup := << >> >a ?
Hn Jn_pq Ln_s Pn Tn Un ^ a> a@ abs acos acosh asin asinh atan atan2 atanh
binomial bye ceil cis clear cmpf cmpq coef complex conjugate cos cosh def deg
degrees delete_variables denom deriv divmod doublefactorial drop dup e eval exp
factorial float floor from_roots fsave gcd get_mpf_prec help hypot imag
improve_roots int integ inv ln lnfactorial log log10 log2 logb long max min mpf
mpq neg nip numer over phase pi prefer_cmpf prefer_cmpq prod py radians ratapx
real restore roots rot round round_n save set_mpf_prec sin sinh solve sqrt
state stats sum swap tan tanh to_fun unique_roots use_degrees use_radians words
words_all |


Note that this method does not allow conditional logic or looping. If you need these operations, program your function in Python and install it.

Use the rpncalc module itself as a source of further examples. Note that additional vocabularies can be defined and added to the search list in priority order.

Installation

The rpncalc package depends on the clnum package. You must install clnum before proceeding. Refer to the clnum user manual for instructions.

The simplest way to install rpncalc is to run the distutils setup script. This will install the package in a standard place which will be on your Python path.

python setup.py install --prefix=/usr/local

If you want to verify that everything is working, you can run the script rpncalc_test.py from the test directory.

You may also want to copy the script rpn.py from the examples directory to some place where you can get to it easily. I put it in the bin directory in my home directory and create the following alias to it.

alias rpn='python -i ~/bin/rpn.py'

The rpn.py script may be customized to suit your needs. For example, you may want to uncomment the line that switches the trig functions into degrees mode.

After you get set up, proceed with the Tutorial.

History

In the mid 1990's, I built an interpreter for a language that was designed for use as an interactive calculator. This program used floating point numbers as its numeric data type. It also supported programming and was very similar to the Forth programming language. It was designed to be extensible in C++. Since it was not mainstream and it was difficult for may users to get used to the RPN notation, I went looking for a programming environment that was interactive and extensible. This lead me to Python circa 1998. Python gave me the same ability to extend it using C/C++ but with a more widely used language. Python is also easier to program than Forth-like languages since you don't need to continuously keep stack effects under control. However, I missed the simplicity of RPN notation for doing simple calculations.

Rather than resurrecting my old interpreter and continuing to maintain it, I decided to write the interpreter included in this package. This has the added advantage that both RPN and the standard Python interpreter are available.

SourceForge.net Logo