Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

mu - Universal unit conversion algebra

GoDoc

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.

Usage

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 special

Canonical form

A 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()  // false

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

Unit algebra

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 ft2

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

Unit metadata

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"

Case sensitivity

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 meter

Case-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 KPA

Each mode caches separately, but units parsed under either mode are interchangeable once parsed.

Decimal values

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, supporting github.com/shopspring/decimal
  • github.com/mattwiller/mu/apd, supporting github.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 Ym to ym (1e48) and the physical constants [h], [k], u and [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 the float64 path, since the decimal Atan/Tan functions have float64 accuracy anyway
  • dec returns mu.ErrRange for 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.

JS / Wasm

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-conv
import { 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.2046226218487757

The raw WebAssembly interface is documented in wasm/README.md.

Limitations

  • 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

License

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.

About

Universal UCUM unit conversion

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages