|
Unit Conversion and Dimensional Analysis Library 3.6.1
A compile-time, header-only C++23 dimensional-analysis library
|
Most units in the library relate to their SI base by a pure multiplicative ratio: a foot is 381/1250 metres, a kilometre is 1000 metres, and converting is a single multiply. Temperature is different. Celsius and Fahrenheit are affine functions of kelvin — they carry a datum offset (a zero-point shift) in addition to a scale factor — so their conversions are not simple ratios and, unlike a pure ratio, are not a reversible linear transform. The header itself notes this:
// NOTE: temperature units have special conversion overloads, since they require translations and // aren't a reversible transform. (temperature.h:53)
This page explains what the datum offset is, how it appears in a conversion, and the one distinction it forces on you: an absolute temperature versus a temperature difference.
A conversion_factor has four parameters (the conversion_factor template in include/units/core.h): a conversion ratio, a base unit/dimension, a π exponent, and a translation ratio — the datum offset. For most units the translation is std::ratio<0>. For the temperature scales it is not (temperature.h:64–68):
Reading these:
The presence of a non-zero translation ratio is exactly what makes a scale affine rather than linear.
When you convert an absolute temperature, the datum offset participates. The library's convert function detects a non-zero translation and adds it after applying the ratio (the translation branches of convert in include/units/core.h). The numbers below are the real output of the program (compile and run to confirm):
Note that 0 °C converts to 32 °F, not 0 °F: the offset is what carries the ice point across. A pure-ratio unit would send 0 to 0; an affine unit does not.
This is the one caveat the datum offset forces.
Caveat (absolute vs difference): a temperature and a temperature interval are different quantities that happen to share a unit name. 20 °C is an absolute point on the Celsius scale; a 1 °C rise is an interval. Converting an absolute point applies the datum offset (20 °C → 293.15 K); converting an interval does not (a 1 °C step is a 1 K step, but a 1 °F step is a 5/9 K step). The unit types in this library model absolute temperatures — every stored value is a point on the scale — so their conversions always carry the offset.
A concrete illustration of why absolute and difference cannot be the same operation: the Celsius and Fahrenheit scales cross at −40, where a single number reads the same on both scales even though the scales are genuinely different. This is a property of the affine mapping, not of any interval:
Because the type stores an absolute point, the practical rule is: do not reach for an affine temperature type to represent a difference. If you need to add or scale temperature intervals freely, work in the absolute (offset-free) scales — kelvin or rankine — where a value and an interval coincide numerically, or keep intervals as plain dimensionless factors and apply them explicitly. Rankine's purely multiplicative definition (no offset) is why 1 K maps cleanly to 1.8 Ra:
A linear transform x ↦ a·x composes and inverts by multiplying and dividing ratios — the machinery the unit manipulators (inverse, squared, sqrt, compound units) rely on. An affine transform x ↦ a·x + b does not: it has an additive term that a multiply cannot express, and the manipulators deliberately drop the translation ratio when they combine units (see inverse_impl in include/units/core.h, which comments that "inverses are rates or changes, so translation factor is removed"). That is why the offset lives only in the direct scale-to-scale conversion path and not in derived-unit algebra — and why the header flags temperature conversions as special, non-reversible transforms rather than ordinary ratio conversions.