Nicolas Martyanoff — Brain dump About

Using units in Emacs Calc

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
  (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.

Share the word!

Liked my article? Follow me on Twitter or on Mastodon to see what I'm up to.