No need to parse (an expression)
Whatever your area of research is, you often need to perform some type of calculation. And of course there are plenty of programs, out there, designed for Mac OS X, to help us crunch numbers. There are graphical calculators and data analysis applications, programs for doing symbolic math and programs written by ourselves to solve specific problems. But sometimes we need someting different. We need a way to type an equation or an expression during runtime, parse and evaluate it. There are plenty of possible reasons you may want a program that parses and evaluates functions and expressions, and I would not try to figure out what are yours. In this article, I would only like to introduce you bc, a versatile command-line program that can be used for such purposes.
bc can be invoked from the Terminal, and of course a full description of its syntax and what it can do is available by typing man bc in the shell. Even though bc can be used interactively, this is not in my opinion the primary usage of this program, since we can rely on much more powerful programs specifically designed for our needs as researchers. But bc becomes really helpful when you need to calculate or evaluate functions, equations, inequalities and expressions from within a context where you don't have easy access to your favourite number-crunching program.
Let's stop being vague, and start with an example. I'm going to develop a very simple AppleScript application, that shows how you can invoke bc, interact with it and capture its results. The choice of AppleScript is just an example: Perl scripts, C programs, Cocoa/Objective-C applications are other possibilities; you will find it easy to port what shown here in your favourite development environment or language.
Open ScriptEditor (in the /Applications/AppleScript folder) and type in the following code:
set defsFile to ((path to me) as string)
set defsFile to (defsFile & "Contents:Resources:bcdefs.txt")
set defsFile to (quoted form of (POSIX path of defsFile))
repeat
set choice to (display dialog "Type an expression" default answer "cos(0)" buttons {"Cancel", "OK"} default button 2)
if the button returned of choice is equal to "OK" then
set expr to the text returned of choice
set cmd to "echo \"" & expr & "\" | /usr/bin/bc -lq " & defsFile
set res to (do shell script cmd)
set choice to (display dialog res buttons {"Quit", "Again"} default button 2)
if the button returned of choice is equal to "Quit" then
exit repeat
end if
end if
end repeat
Save it with the name you prefer, but save it as an "Application Bundle". Then, using a text editor like TextEdit (in simple text mode), SubEthaEdit, TextWrangler or Smultron, create a new document and type in the following:
define min(x,y) {
if(x<=y) {
return x;
}
return y;
}
define max(x,y) {
if(x<=y) {
return y;
}
return x;
}
define sin(x) {
return s(x);
}
define cos(x) {
return c(x);
}
define tan(x) {
if(cos(x)!=0) {
return sin(x)/cos(x);
}
return 10^300;
}
define ln(x) {
return l(x);
}
define exp(x) {
return e(x);
}
define pi() {
return 3.141592653589793;
}
Save it as bcdefs.txt. Then, in the Finder, right-click on the application you created before with ScriptEditor and choose Show Package Contents; then move the bcdefs.txt file in the Contents/Resources folder of the application bundle.
Now our simple application is ready to be launched. The first three lines of the AppleScript code just locate the bcdefs.txt file into the application bundle's Resources folder. Then a continuous loop repeats a set of instructions until the user chooses to exit. A display dialog instruction asks for an expression to be entered in the text field; it is then extracted and stored into the expr variable. Then all the magic happens.
The bc command is invoked with two options and one argument:
- the -q option just sets the "quiet mode": bc runs without printing its version number and copyright and warranty disclaimers, that are of little use to us at the moment;
- the -l option specifies that we want bc to use an external file as a math library (we're going to return to this later on);
- and finally the argument is specified, i.e. the path to the bcdefs.txt file, that's our math library file.
When the bc command is invoked with the above syntax, we need to feed it with some input (e.g. an expression). Since we don't want to start an interactive session, we use the piping magic of *nix systems together with the simple echo command (that blindly repeats what's enclosed in quotes as an argument, namely what's stored in the expr variable). So, if you typed 2+exp(-3) in the text field, our simple AppleScript invokes a shell to execute the following command:
echo "2+exp(-3)" | /usr/bin/bc -lq '/path/to/app_bundle.app/Contents/Resources/bcdefs.txt'
This is exactly what you can type in a shell (for example a Terminal window), with the proper path to the application of course, and exactly what you can pass to any function of any language that executes shell commands (like Perl's backquotes or Cocoa's NSTask class). The result, you'll hardly believe this, is 2.04978706836786394297.
So what's so important about bc? Isn't it just another "calculator"? No it's not. For two important reasons:
- bc does not only perform calculations; it evaluates expressions. Try this: enter 2/tan(0.16) > 0.21 in the text field, and see the result. It's the number 1. No, it's the logic value 1, or true. bc evaluated the inequality, and determined that the left hand side is greater than the right hand side, so the inequality is true. Try this: try 1>0 || 1+1==3. Guess what? This expression too returns 1, since its first part is always true, and the logical or makes the rest. You can combine calculations, functions, inequalities, logical tests and whatever, and bc will be happy to do its best and return a value: numerical, or logical, since it's the same; details on the (pretty standard) syntax for logical tests are reported on bc's man page (which you are strongly encouraged to read);
- bc, in its default implementation, isn't very friendly: the sin() function is defined as s(), so cos() is defined as c(). A tan() function is not even implemented, and overall it seems that whoever developed bc thought that built-in functions weren't necessary. Fortunately, you can expand bc with math library files: that's what that bcdefs.txt file stands for. Its syntax is very simple (and well documented in bc's man pages), and basically consists on a list of defines that oddly enough define new functions. They can accept zero, one or more arguments, and their implementation is enclosed in braces. A few examples are given in our bcdefs.txt files, that implements the sin(), cos(), ln() and exp() functions based on the built-in s(), c(), l() and e() functions respectively, and adds the tan() function and two more functions (max() and min() that return the maximum and minimum, respectively, of their two arguments) and another function (pi()) that is actually a constant implemented as a function. In this way, you can evaluate an expression like this: max(sin(pi()/3),cos(pi()/4)) > ln(3/sqrt(2)) and discover that the inequality is true. Nice, isn't it?
bc is not a solution to for all problems, and more often than not it's not a solution at all, since most of the number-crunching operations we usually perform for our research activity are nicely carried out by other specific and powerful programs. But sometimes a way to evaluate a function or a numerical or logical expression defined at runtime is necessary, and bc is sufficiently versatile to be employed from within shell scripts, Perl or Python scripts, AppleScripts, C or Cocoa/Objective-C programs etc.; moreover, bc capabilities can be easily expanded, and it's really easy to put repeated calls to bc with expressions with an argument that is incremented at each cycle, so to plot a function or an inequality, or to find extrema or other features of a function using a simple, custom made script or program that's able, with no effort at all, to parse and evaluate numerical and logical (but not symbolic, unfortunately) expressions.



Comments
CalcPad
I use the Dashboard Widget CalcPad (http://www.imaginet.ne.jp/~tambe/dashboard/CalcPad_en.html) as an easy-to-start calculator. You could choose to use bc to drive that and thanks to your article I added your library by saving it in my home and defining the command in the widget. Works great!
/Fredrik
Great!
Great! : ) And nice widget, I'm downloading it right now to give it a try!
calc
I just added this line to my .cshrc:
alias calc 'awk "BEGIN{ print \!* }" '
and now you have your terminal calculator ;-)
roberto% calc 2+exp(-3)
2.04979