Fast numeric conversion between units of measurement, using the UCUM standard. The library is available natively in Go, or as a WebAssembly module via npm.
The package embeds the UCUM v2.2 tables of conversion factors, and converts numeric quantities between
commensurable units over float64 and math/big.Rat.
Fixed-precision decimal conversion is available in the subpackages
mu/dec (github.com/shopspring/decimal) and
mu/apd (github.com/cockroachdb/apd/v3).
v, err := mu.Convert(1, "kg", "[lb_av]") // 2.2046226218487757
t, err := mu.Convert(100, "Cel", "[degF]") // 212
// Validation and inspection
u, err := mu.Parse("kg.m/s2")
err = mu.Validate("mm[Hg]")
ok, err := mu.Commensurable("B[W]", "W") // true: dimensioned specialA parsed unit reduces to an exact magnitude over the seven UCUM base units. The rendered form carries no magnitude and always parses back to the same canonical form, so it works as an equivalence key for indexing or comparison.
u, err := mu.Parse("mm[Hg].min/L")
c := u.Canonical()
c.String() // "g/m4/s"
c.Magnitude() // *big.Rat, exact: 7999320000
c.Float64() // 7.99932e+09
c.Dimension() // {-4, -1, 1, 0, 0, 0, 0}
p, _ := mu.Parse("Pa.s/m3")
c.Commensurable(p.Canonical()) // true
c.Equal(p.Canonical()) // false: same dimension, different magnitude
u.Expanded() // "7999320000.g/m4/s": the same units, magnitude included
u.Dimension() // same vector, straight off the Unit
u.IsSpecial() // false
u.IsArbitrary() // falseFor special units, the canonical form is the conversion function's defining unit; however, the magnitude is unset.
For example, Cel canonicalizes to K with (incorrect) magnitude 1: IsSpecial reports this condition.
Units can be combined by multiplication, division and exponentiation:
m, _ := mu.Parse("m") // meters
s, _ := mu.Parse("s") // seconds
ft2, _ := mu.Parse("[ft_i]2") // square feet
area, _ := m.Mul(m) // "m2"
speed, _ := m.Div(s) // "m/s"
vol, _ := m.Pow(3) // "m3"
back, _ := speed.Mul(s) // "m": composition cancels
conv, _ := mu.NewConverterFromUnits(area, ft2)
conv.Float64(12) // 129.16692500051667 ft2Special units (e.g. Cel, B[W], [pH]) cannot be composed and return ErrSpecialContext.
Pow accepts exponents in ±128; a dimension exponent outside of int8 range returns ErrSyntax.
The embedded UCUM data tables are readable directly:
info, ok := mu.LookupUnit("N")
info.Names // ["newton"]
info.PrintSymbol // "N"
info.Property // "force"
info.Class // "si"
info.IsMetric // true
info.Dimension // mu.Dimension{1, -2, 1, 0, 0, 0, 0}
info.Definition // {Value: "1", Unit: "kg.m/s2"}
mu.LookupUnit("CEL") // not found: lookup is case-sensitive by default
mu.CaseInsensitive.LookupUnit("CEL") // the other variant's alphabet
mu.LookupUnit("kg") // not found: prefixes are not stripped
mu.LookupPrefix("k") // {Code: "k", Names: ["kilo"], Value: 1000}
for _, u := range mu.Units() { ... } // all 312 atoms, base units first
for _, p := range mu.Prefixes() { ... } // all 24, descending magnitude
mu.Version // "2.2"
mu.Revision // "2024-06-17"UCUM defines both case-sensitive and case-insensitive code sets with distinct symbols for some units. The package-level functions parse case-sensitively, e.g.:
mu.Parse("kg") // ok
mu.Parse("Kg") // error: no case-sensitive prefix "K"
mu.Parse("kg.M") // error: "M" is the case-insensitive code for meterCase-insensitive matching is also available:
mu.CaseInsensitive.Parse("KG") // ok
mu.CaseInsensitive.Convert(100, "CEL", "[DEGF]") // 212
mu.CaseInsensitive.Validate("kpal") // nil (valid): kPa's CI code is KPAL, not KPAEach mode caches separately, but units parsed under either mode are interchangeable once parsed.
The main package does not import a decimal library: fixed-precision decimal conversion is provided by a pair of companion packages built on top of the exact rational core:
github.com/mattwiller/mu/dec, supportinggithub.com/shopspring/decimalgithub.com/mattwiller/mu/apd, supportinggithub.com/cockroachdb/apd/v3
import "github.com/mattwiller/mu/dec"
d, err := dec.Convert(decimal.New(1, 0), "kg", "[lb_av]") // 2.20462262184877580722973801345027
cv, err := dec.CaseInsensitive.NewConverter("CEL", "[DEGF]")- Decimal representations are arbitrary-precision, so every UCUM conversion factor is representable,
including extremes such as
Ymtoym(1e48) and the physical constants[h],[k],uand[m_e]. Results are rounded to 34 significant digits (dec.Precision), so output width is bounded regardless of input scale - Transcendental special units are evaluated natively at 34 significant digits where the decimal library supports
the function. Some (
[p'diop],%[slope],[hp'_Q]) use thefloat64path, since the decimalAtan/Tanfunctions have float64 accuracy anyway decreturnsmu.ErrRangefor a domain error of a transcendental special unit, such as the logarithm of a magnitude less than or equal to zero
The same interface is also available using github.com/cockroachdb/apd/v3, with *apd.Decimal as the value type:
import "github.com/mattwiller/mu/apd"
d, err := apd.Convert(apd.New(1, 0), "kg", "[lb_av]") // 2.20462262184877580722973801345027
cv, err := apd.CaseInsensitive.NewConverter("CEL", "[DEGF]")The two backends produce the same result to 34 significant digits wherever both answer, but their working ranges
differ at the extremes. apd evaluates transcendental functions at a significant-digit precision natively and carries
results as far as apd.MinExponent; dec works in decimal places underneath, where the cost of an evaluation climbs
superquadratically with depth, so it reports mu.ErrRange for a transcendental result below roughly 1e-1000 instead.
Neither ever returns a silently flushed zero. Conversion factors and ordinary arithmetic are unaffected in both.
Converter.Coefficients returns the exact rational coefficients of a linear conversion (y = a*x + b),
and Converter.SpecialEnds describes both ends of a conversion through a transcendental special unit.
mu/dec and mu/apd are built entirely on these two methods; they can be used to support other value types as well.
The library is also available via npm as mu-conv: it uses the same conversion core compiled to WebAssembly,
with the UCUM tables embedded and no dependencies.
npm install mu-convimport { load } from "mu-conv";
const mu = await load(); // Hydrate conversion factors from embedded data tables, ~7 ms
const pounds = mu.convert(1, "kg", "[lb_av]"); // 2.2046226218487757The raw WebAssembly interface is documented in wasm/README.md.
- Unit codes are limited to 255 bytes
- Parentheses in units can be nested up to 10 levels deep
- Value magnitudes are capped at 2,048 bits in the rational core
Copyright 2026 Matt Willer. Licensed under the BSD 3-Clause License.
UCUM unit data is copyright 1999-2024 Regenstrief Institute, Inc. All rights reserved. Licensed under the UCUM License, Version 1.1 (the "License"); you may not use this data except in compliance with the License.