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.
Supports the standard Python numeric data types (integer, long integer, float, and complex).
Uses the Class Library for Numbers (CLN) to provide rationals, extended precision floating point, and their complex counterparts. Defaults to the CLN numeric types. Requires an explicit conversion to Python float and complex.
Preserves the user's data when exceptions are raised. This helps the user to debug a calculation or recover from the error and continue.
Division of two integers produces a rational.
Supports the Python built-in functions that apply to numbers.
Provides extended precision versions of the functions in the standard math and cmath libraries.
Allows user installation of additional Python functions. This is the primary mechanism for programming the calculator.
Supports smooth transition between RPN and Python interpreters. This allows a user to use the interpreter that is most convenient for solving a particular problem.
Avoids the complexity of making the RPN interpreter into a full programming language. In my opinion, Python is a lot easier to program in than a language like Forth (the classic RPN programming language). I have used Forth and Forth-like languages for over 20 years. I do not think the RPN interpreter should be turned into yet another Forth-like language.
Leaves the hard things like arrays to the Python interpreter. Inventing RPN notation for something Python handles well is a waste of time. Much better to simply switch over to the Python interpreter.
Python interface to Class Library for Numbers
Polynomials and Rational Functions
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
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
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.
Numbers and operators in the RPN interpreter are separated by white space.
Operands precede operators (hence the name Reverse Polish Notation).
The top of the stack is printed by the "dot" operator (a period character). The value is removed from the stack by this operator.
Rational numbers are expressed in a more obvious form.
A print statement is required to get Python to print values in the same form as the RPN interpreter.
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
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
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
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 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> 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 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.
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.
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.