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:
- the
CALC_SCALE
environment variable's name was too long for convenient use on the command line, so thesc
environment variable can also be used:sc
is intended for use with a single invocation ofcalc
, whileCALC_SCALE
should be used over larger scopes - if the program's basename is an equals sign (=) preceded by one, two or three digits then the number that those digits represent will be the default number of digits after the decimal
(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):
- initial commit
- can use 'x' in place of '*'
- can use environment variable to set digits after decimal
sc
environment variable
can use shorter - 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!