|
Unit Conversion and Dimensional Analysis Library 3.6.1
A compile-time, header-only C++23 dimensional-analysis library
|
A quantity is a number and a unit. This library makes the unit part of the type, so the compiler enforces it — and produces no code to do so.
A physical quantity carries two pieces of information: a magnitude and a unit. Conventional programs represent only the first. A double distance stores 1500.0; whether that is meters, feet, or nautical miles lives in a comment, a variable name, or the programmer's memory. Nothing in the type system distinguishes a distance from a duration, a distance in meters from the same distance in feet, or a valid assignment from a silent unit mismatch.
units closes that gap. A quantity such as meters<double> is a distinct type; its unit is a template parameter, checked at compile time and erased before code generation. Mixing incompatible units is a compilation error, not a wrong answer at run time.
The canonical failure is the loss of the Mars Climate Orbiter in 1999. One software component produced impulse values in pound-force seconds; the component consuming them expected newton seconds. Both were double. The types agreed, the code compiled, the numbers flowed, and the spacecraft's trajectory was off by the ratio between the two units — roughly a factor of 4.45. No tool in the build could see the error, because to the compiler there was no error: two doubles were being passed around.
This is the general shape of the unit-mismatch bug:
Related members of the same family: adding a length to a time; assigning an area to a length variable; passing an angle in degrees to a routine that expects radians; forgetting a conversion entirely.
With typed quantities, each of these is rejected where it is written:
The length + elapsed line is not a run-time check that throws; it is a type error the compiler reports before the program is ever run. The mismatch cannot reach a test, a review, or a mission.
When the enforcement happens determines what it costs and what it can catch.
Run-time unit checking — tagging each value with a unit identifier and comparing tags during arithmetic — is possible, but it pays for every operation with a branch and a comparison, and it can only report a mismatch once execution reaches the offending line with the offending data. A path that a test never exercises is a path never checked.
units performs all dimensional analysis during compilation. The unit of every intermediate result is computed from the units of its operands by the type system: meters / seconds is a velocity type, meters * meters is an area type, and there is no representation in which a length and a time can be added. Because the check is structural, it covers every path through the code whether or not a test runs it, and because it is resolved at compile time, none of it survives into the emitted program.
Note: compile-time checking catches dimensional errors — mixing units that do not belong together. It does not catch a magnitude error you write within a single unit (storing 9.8 where you meant 98.0 in the same unit is still a plain numeric mistake). What it removes is the entire conversion-and-mismatch category, which is where the expensive, hard-to-find defects live.
The informal alternative is discipline: name the variable distance_m, document the unit in the API, review carefully. This holds only as well as the least careful change to touch the code, and it degrades under the conditions where correctness matters most — a large codebase, many contributors, a refactor that renames one side but not the other, an interface boundary where the convention on each side was decided independently.
A naming convention has three structural weaknesses that a type does not:
A typed quantity makes the unit a property the compiler tracks, propagates through arithmetic, and converts implicitly and correctly when — and only when — the dimensions match:
The conversion factor is applied by the library, from a single authoritative definition, in one place — not re-derived and re-typed at every call site where a human might get it wrong.
Wrapping every number in a class, tracking units, and inserting conversions does not slow the program down. The safety is a property of the types, and types do not exist at run time.
The result is that an optimized build of code using units produces the same machine code as the equivalent hand-written double arithmetic — with the mismatches removed. The full argument, with a worked example of a multi-step conversion collapsing to a single multiply, is in efficiency.
A units library is the right tool for code that manipulates physical quantities. It is not a universal wrapper for every number, and a few situations argue against it:
Outside those cases, the cost is a template dependency and the syntax to learn, against the elimination of an entire bug class at no run-time expense.
See also: efficiency · the namespace map · type safety · the cheat sheet