I was recently reminded of the ability of Emacs Calc to perform computations on values with units. Like so many Emacs features, it was on the list of the things I wanted to explore. Today is the day to do so!
Basic operations with units
The Emacs Calc manual presents a lot of features but makes it hard to
understand how to start. You cannot just enter an expression such as “25mm” in
calc-mode
: 25 will be correctly interpreted as a number, but “mm” will be
used to trigger the m m
key binding which executes calc-save-modes
. Not
what you had in mind.
To enter a value with a unit, use the '
(apostrophe) key to enter an
algebraic expression and hit return
to push it on the stack. You can now use
this value for your computations.
Note that after hitting '
, you can use it a second time to recall the
expression on the stack and edit it.
To remove the unit associated with a value. Simply hit u r
, for “unit
remove”.
Simplifying expressions
Expressions containing multiple units can be simplified. For example, enter
two values, 2m
and 50cm
and add them with +
. The resulting expression is
2 m + 50 cm
. Hit u s
, for “unit simplify”, to have Calc simplify
it to 2.5 m
.
Converting units
Unit conversion is of course possible. Use u c
, for “unit convert”,
at any moment: Calc will ask for a unit and will try to convert the value at
the top of the stack into this unit. For example, entering 1.5mi
(mi
being
the unit for miles) and typing u c km <return>
will correctly yield
2.414016 km
.
You will quickly discover that Calc tries way too hard to convert units and
will produce really strange results when types are not compatible. For
example, trying to convert 250mA
into kilometers will produce 0.25A
which
is clearly incorrect. I have no idea what causes this behaviour. Fortunately
you can use u n
, bound to calc-convert-exact-units
, which will check that
the unit you required is compatible with the unit of the expression on the
stack. To minimize the risk of producing incorrect results by mistake, I
replace the calc-convert-units
function by calc-convert-exact-units
. I
would prefer to remap u c
, but Calc has a strange way to handle key bindings
that prevent doing this kind of remapping.
(use-package calc
:config
(require 'calc-units)
(setf (symbol-function 'calc-convert-units)
(symbol-function 'calc-convert-exact-units)))
As a shortcut, u b
, where b
stands for “base”, will convert the value
to its base unit. For example, it will automatically convert 250mA
to
0.25A
.
Defining custom units
Calc comes by default with more than 170 units (type u v
to list them all),
but none of them are about standard data size units. This is disappointing
given that Emacs is a software primarily used for programming. But we can add
new units, so it is not that much of a problem.
New units can be defined with the math-additional-units
variable. Its format
is the same as math-standard-units
: each entry is a list of three elements:
- A symbol identifying the unit.
- An expression indicating the value of the unit, or
nil
for fundamental units. - A textual description.
Let us add data sizes. We need two units, bits and bytes, with bytes being
defined as 8 bits. Note that using the b
symbol for the bit unit will
override the internal “Barn” unit; not a problem to me, I do not expect to use
it any time soon. Feel free to use bit
instead.
(setq math-additional-units
'((b nil "Bit")
(B "8 * b" "Byte")))
(setq math-units-table nil)
We need to set math-units-table
to nil
after defining new units to force
Calc to recompute its internal table.
These new units can of course be used with standard SI prefixes defined in
Calc. For example, 25kB
will correctly be converted to 25000 B
. And asking
for a convertion to the base unit will yield 200000 b
.
A limitation of Calc is its inability to handle unit prefixes of more than one
character, so we cannot define ki
as being a prefix with a multiplicative
value of 1024, which would for example allow us to manipulate kibibytes.
A workaround is to define these power-of-two binary units with the prefix included:
(setq math-additional-units
'((b nil "Bit")
(B "8 * b" "Byte")
(kiB "2^10 * B" "Kibibyte")
(MiB "2^20 * B" "Mebibyte")
(GiB "2^30 * B" "Gibibyte")
(TiB "2^40 * B" "Tebibyte")
(PiB "2^50 * B" "Pebibyte")
(EiB "2^60 * B" "Exbibyte")))
(setq math-units-table nil)
Very helpful for data size conversion.
Going farther with Calc
Writing this post has been an interesting experience: it forced me to read various parts of the Calc manual and some of its source files. It made me realize that this package is full of useful features. I hope to find the time to experiment with Calc features in the future.