At CoolPlanet, we needed unit conversion in the browser. Similar story to porting psychrometric calculations to JavaScript: the back end already knew how to convert between units, and the front end needed to mirror those conversions exactly.
Doing the work client-side opened up a lot of flexibility. The unit our APIs responded in didn't really matter any more; we could transform on the fly. User preferences could be applied per user without round-tripping to the back end. A senior executive in North America might want imperial; an engineer in Germany might want metric. Same data, different presentation.
The breadth of quantities ruled out hardcoded conversions. Power, energy, mass flow, volumetric flow, temperature, pressure, each with metric and imperial variants. We needed a system that understood units from first principles.
Dimensions
There are seven SI base units:
A unit's dimension is its ratio of the seven base units. Joule is kg·m²·s⁻²; Pascal is kg·m⁻¹·s⁻². Have a poke at the explorer below. Try switching between Celsius and Fahrenheit (same dimensions), or pick Watt to see what shares its dimensional fingerprint.
Every unit reduces to a ratio of the seven base units. Pick one to see the breakdown.
Other units with these same dimensions:
Some units don't fit this model (percent, for example) and are represented as dimensionless: every base unit exponent is zero.
One thing worth flagging before we go further: matching dimensions is necessary for a conversion to make sense, but it isn't sufficient. Several quantities share dimensional fingerprints while describing entirely different things.
Dimensional analysis can't catch these collisions on its own.
- Wwatt
- kWkilowatt
- MWmegawatt
- BTU/hBTU per hour
- hphorsepower
- VAvolt-ampere
- kVAkilovolt-ampere
- mVAmegavolt-ampere
- VARvolt-ampere reactive
- kVARkilovolt-ampere reactive
- MVARmegavolt-ampere reactive
Same dimensional fingerprint, but converting kW to kVA isn't physically meaningful. Each describes a different aspect of an AC system.
Dimensional analysis on its own can't catch these collisions. The fix is to group units by quantity (power, apparent power, reactive power, energy, apparent energy, reactive energy…) and only allow conversions within a quantity.
Unit Conversion
If units have the same dimensions, you can convert between them using scales and offsets. Most units I've come across only need to be scaled; some, such as temperature, also need an offset.
Start with a simple expression we know to be true:
We need to express this as pure arithmetic, since the system can only see numbers, not unit strings. Strip the units away and the equation falls apart:
A scale closes the gap. Calling metres A and centimetres B, with subscripts for the numeric value (n) and scale (s), we can rebalance the equation:
Where do the scales come from? For each set of dimensions, one unit is chosen as the base and assigned a scale of 1. Every other unit with those dimensions is defined relative to it. Above, metre is the base, and the centimetre's scale of 0.01 says "one centimetre is 0.01 metres".
A scale on its own covers most conversions, but not temperature. Zero degrees Celsius is 32 degrees Fahrenheit:
This time a scale can't close the gap on its own; zero multiplied by anything is still zero. We need an offset too. Adding a subscript o for offset to each side gives the full balance equation:
The base unit for temperature is Kelvin, so scales and offsets are defined relative to it. Celsius has scale 1 and offset 273.15, since 0°C = 273.15K. Fahrenheit has scale and offset 255.37, since a 1°F step is of a Kelvin step and 0°F = 255.37K:
A little algebra rearranges the balance equation into a formula for converting any numeric value in B into A:
Toggle between length and temperature in the walkthrough below to see the scale-only and scale-plus-offset cases resolve.
Plug in a value and watch the formula resolve.
| Unit | Scale (s) | Offset (o) |
|---|---|---|
| KKelvin | 1 | 0 |
| °CCelsius | 1 | 273.15 |
| °FFahrenheit | 0.55556 | 255.37222 |
Unit Database
We need every unit defined: its dimensions, scale, and offset. Thankfully, the good people of Project Haystack maintain exactly this. Their unit system documentation is where I learnt the conversion methodology above.
The database is a simple txt file. Each row defines a single unit using this format, with semicolons separating the structural fields and commas separating the identifiers within the first field:
Pick a row and see how each part maps to the format.
The full case: every field present, including an offset.
At the time of writing, this was the full units.txt file.
Converting Across Time
Most of the data we deal with is time-series, so there's one extra wrinkle worth covering: converting between a rate and its integral.
Take power and energy. Power is in kg·m²·s⁻³; energy is in kg·m²·s⁻². They differ by one power of seconds, and a duration provides exactly that factor. The same relationship holds for several other pairs:
When a rate is constant, the integral is just rate × time: 5 kW held for 2 hours is 10 kWh. When the rate changes, you sum the contributions of each segment, which is all an "area under the curve" calculation reduces to. The live converter directly below visualises this for any rate quantity.
Try It Yourself
The widget below puts the formula and a subset of the Haystack unit database to work. Pick a quantity, change the value, and watch every other unit in the same quantity update. For rate quantities (power, mass flow, volumetric flow) the across-time section appears, integrating segment-by-segment.
Pick a quantity and a value. Every other unit in the same quantity is computed in the browser.
| Wwatt | 5,000 |
| MWmegawatt | 0.005000 |
| BTU/hBTU per hour | 17,072.13 |
| hphorsepower | 6.7051 |
Across time: kW→Energy
Define one or more constant-rate segments. Area under the curve is the integral.
Total energy across 6 h (area under the curve):
| Jjoule | 1.296e+8 |
| kJkilojoule | 129,600 |
| MJmegajoule | 129.6 |
| kWhkilowatt-hour | 36 |
| BTUBritish thermal unit | 122,860.84 |
Conclusion
With the conversion formula above and Project Haystack's unit database, it's fairly straightforward to build your own unit converter. That's the foundation we landed on at CoolPlanet: a TypeScript implementation fed by the Haystack units file, mirroring the back-end conversions exactly. From that point on, the unit our APIs returned was decoupled from the unit a user saw. Convert client-side, instantly, against whatever preference is set. Same data, in whatever units make sense to whoever's looking at it.