Skip to content

Blog Zero

02 Nov 2021

Calculating on the Command Line

Note: there's a public repository containing the source code and any formal documentation for the program that this post is about.

There are any number of ways to get your computer to do math, but I wanted a command line program to quickly, easily and in as natural a way as possible do basic calculations like summing a few numbers, calculating an item's after-tax price or doing basic unit conversions. I also wanted it to operate on both integer and real values and give results to a reasonable number of decimal places.

Of course, I wasn't going to implement the part that did the actual math since existing programs already do that. One of the earliest versions of my calc script was just a minimal wrapper around one such program: bc. Aside from code to output a usage message my script's implementation was basically

echo "scale=2; $*" | bc

which combined all of the command line arguments into a single expression for bc to evaluate, after setting the default number of digits after the decimal to 2 (which is sufficient for most calculations and ideal for ones involving (my local) currency). Note that the result from bc may be an integer or may have fewer or more than the default number of digits after the decimal depending on what's appropriate for the particular computation.

(Also note that the default number of digits after the decimal affects intermediate as well as final results of calculations, and so the result of a calculation with fewer digits after the decimal may not just be a rounded - or even truncated - version of the result of the same calculation done with more digits after the decimal.)

This version worked pretty well, especially with = (that is, a single equals sign) as a symbolic link to calc:

= 2 + 2
= 12.34 + 56.78 + 90.12
= 5 / 3
= '42.5 * 17'
= 42.5 \* 17
4
159.24
1.66
722.5
722.5

Having to quote or escape multiplication signs to protect them from being expanded by the shell into every file in the current directory was less than ideal, but a simple - if a little brute-force - change

echo "scale=2; $*" | tr 'x' '*' | bc

fixed that. Now the 'x' character could be used as a multiplication sign as well:

= 42.5 x 17
722.5

So now the four basic mathematical operations could be used without having to quote or escape anything. Parentheses still required - and still do require - escaping or (usually easier) quoting, however:

= '(23.40 + 12.34) x 1.12'
40.02

The final tweak - until very recently - was to allow the default number of digits after the decimal to be customized on a per-session or a per-command basis. The implementation effectively became

echo "scale=${CALC_SCALE:-2}; $*" | tr 'x' '*' | bc

which allowed the CALC_SCALE environment variable to be used to specify that default:

= 5 / 3
export CALC_SCALE=4
= 5 / 3
CALC_SCALE=8 = 5 / 3
= 5 / 3
CALC_SCALE=
= 5 / 3
1.66
1.6666
1.66666666
1.6666
1.66

And that's where it stood until I looked at it again in preparation for writing this post. While it's still not a very long script its core is definitely no longer a one-liner. But calculations can now include the mathematical constants pi and e, as well as the sin(), cos(), atan(), ln() and exp() functions, and the default number of digits after decimal is now easier to customize:

(A program can be given an additional basename by creating a symbolic link to it: for example, executing ln -s calc 15= will create a symbolic link that can be used to perform calculations with a default of 15 digits after the decimal place.)

So calculations like the following are now possible:

= 2 x pi x pi
export CALC_SCALE=5
= 2 x pi x pi
sc=8 = 2 x pi x pi
= 2 x pi x pi
8= 2 x pi x pi
9= 'sin(1.2) x sin(1.2) + cos(1.2) x cos(1.2)'
sc=10 = pi
19.71
19.73917
19.73920875
19.73917
19.73920875
0.999999996
3.1415926535

Will I use this additional functionality often enough to warrant the added complexity? I'm not sure. But if I didn't add it then there wouldn't be any way for me to find out. It's not a huge increase in absolute complexity in this case (even if it is in relative complexity), but I think the same reasoning applies to other, larger programs as well. Of course, I could always remove the new code if it turns out I don't use it, but who ever does that?

As a final note, Emacs users can use the quick-calc command to access similar functionality, as I recently found out. Well, rediscovered actually, since it turns out that I've had a keybinding for it in my Emacs configuration since 2018 and had completely forgotten about it: a not uncommon occurrence.

Selected Commit History

Some selected dates in the calc script's original commit history (which is different from its GitHub commit history):

  • <2007-07-16 Mon> initial commit
  • <2015-08-31 Mon> can use 'x' in place of '*'
  • <2017-03-31 Fri> can use environment variable to set digits after decimal
  • <2021-11-24 Wed> can use shorter sc environment variable
  • <2021-11-25 Thu> can use pi, e, sin(), cos(), atan(), ln() and exp() in calculations

If you'd like to support this site then check out my web app InEveryNook.com and see if it's of interest to you, pass it along to anyone you think might be interested in it, or both. Thanks!

Tags: shell software