AXLR / Catalog / mathematics

unit_convert

Convert quantities between units with a dimensional check and an exact conversion factor: SI units with all prefixes, units accepted with the SI, and US customary and imperial units from a bundled dated table built from the SI Brochure (9th ed.) and NIST SP 811. Parses compound units (km/h, kg*m/s^2, J/(kg*K), kWh, m²) and converts temperatures in degC, degF, degR and K with their offsets.

Install

/tools install unit_convert
/tools install unit_convert@1.0.0

In a console session, or let the model find it in normal mode: axlr_catalog describes it and axlr_install_tool asks for the same install card. The card reads catalog axlr · unit_convert 1.0.0 · verified once the index signature, the manifest digest and every file digest check out. Then the model runs it with axlr_run_tool unit_convert; each run is approved as a local_exec.

Arguments

One JSON object on stdin, validated against the schema before the program starts.

nametyperequireddefaultmeaning
valuenumber | stringno1Number or decimal string
fromstringnoUnit expression: names, SI prefixes, * / ^ ( ), juxtaposition, superscripts. length ≤ 200
tostringnoOmitted: SI base units. length ≤ 200
temperaturestringno"absolute"Bare temperature units: with offsets (absolute) or as intervals. one of "absolute", "difference"
precisionintegerno15Significant digits when the decimal does not terminate. ≥ 1; ≤ 50
list_unitsbooleannoList the table's units, prefixes and sources
Input schema (JSON)
{
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "value": {
      "type": [
        "number",
        "string"
      ],
      "default": 1,
      "description": "Number or decimal string"
    },
    "from": {
      "type": "string",
      "maxLength": 200,
      "description": "Unit expression: names, SI prefixes, * / ^ ( ), juxtaposition, superscripts"
    },
    "to": {
      "type": "string",
      "maxLength": 200,
      "description": "Omitted: SI base units"
    },
    "temperature": {
      "type": "string",
      "enum": [
        "absolute",
        "difference"
      ],
      "default": "absolute",
      "description": "Bare temperature units: with offsets (absolute) or as intervals"
    },
    "precision": {
      "type": "integer",
      "minimum": 1,
      "maximum": 50,
      "default": 15,
      "description": "Significant digits when the decimal does not terminate"
    },
    "list_units": {
      "type": "boolean",
      "description": "List the table's units, prefixes and sources"
    }
  }
}

Example

Input

{
  "value": 100,
  "from": "km/h",
  "to": "m/s"
}

Output (stdout)

{
  "result_exact": "250/9",
  "result": "27.7777777777778",
  "dimension": "m s^-1"
}

Files

The command the install card registers is python3 unit_convert.py, run in the workspace with PATH and HOME as its only environment. Each file's SHA-256 is in the signed index; the console checks them before showing the card and again before writing.

unit_convert.py · sha256 e0a04af242074d2574b7eb1314a68bacf1ec2545e01b3abc26f96ba9c2827689
#!/usr/bin/env python3
"""Convert a quantity between units with a dimensional check, using the bundled table
units.json (exact definitions from the SI Brochure, 9th ed., and NIST SP 811). Compound units
are parsed ("km/h", "kg*m/s^2", "J/(kg*K)", "kWh", "m²"), SI prefixes attach to SI and
SI-accepted units, and the conversion factor is an exact fraction. Temperatures in degC, degF,
degR and K are converted with their offsets when the whole unit is one temperature; inside a
compound unit (or with temperature "difference") they are temperature differences.

Input (JSON on stdin):
  value        number or decimal string, default 1
  from         unit expression
  to           unit expression; omitted: SI base units
  temperature  "absolute" (default) | "difference"
  precision    significant digits of the decimal rendering of a non-terminating result, default 15
  list_units   true: list the table instead
"""
import json
import pathlib
import re
import sys
from decimal import Decimal, localcontext
from fractions import Fraction

SUPERSCRIPTS = {"⁰": "0", "¹": "1", "²": "2", "³": "3", "⁴": "4", "⁵": "5", "⁶": "6", "⁷": "7", "⁸": "8", "⁹": "9", "⁻": "-"}
TOKEN = re.compile(r"\s*(?:(?P<name>(?:[^\W\d]|[°%′″])+)(?P<attached>-?\d+)?|(?P<number>\d+)|(?P<op>\*\*|[*/^()·×.+-]))")


def fail(message):
    print(json.dumps({"error": message}, ensure_ascii=False))
    sys.exit(2)


def load():
    return json.loads((pathlib.Path(__file__).parent / "units.json").read_text(encoding="utf-8"))


TABLE = load()
DIMS = TABLE["dimensions"]
UNITS = TABLE["units"]
PREFIXES = {p: Fraction(v) for p, v in TABLE["prefixes"].items()}
NAMES = {}
for _symbol, _unit in UNITS.items():
    NAMES[_symbol] = _symbol
    for _alias in _unit["aliases"]:
        NAMES.setdefault(_alias, _symbol)
TEMPERATURE_SYMBOLS = {"K", "degC", "degF", "degR"}


class Quantity:
    def __init__(self, factor=Fraction(1), dims=None, exact=True):
        self.factor, self.dims, self.exact = factor, dims or [0] * len(DIMS), exact

    def mul(self, other, sign=1):
        factor = self.factor * other.factor if sign == 1 else self.factor / other.factor
        return Quantity(factor, [a + sign * b for a, b in zip(self.dims, other.dims)], self.exact and other.exact)

    def power(self, n):
        return Quantity(self.factor ** n, [a * n for a in self.dims], self.exact)


def unit_quantity(symbol, prefix=None):
    unit = UNITS[symbol]
    if unit["exact"]:
        factor, exact = Fraction(unit["factor"]), True
    else:
        factor, exact = Fraction(Decimal(unit["factor_decimal"])), False
    if prefix:
        factor *= PREFIXES[prefix]
    return Quantity(factor, list(unit["dimension"]), exact)


def resolve(name):
    """(symbol, prefix) for a unit name: an exact symbol or alias first, then prefix + unit."""
    if name in NAMES:
        return NAMES[name], None
    for prefix in sorted(PREFIXES, key=len, reverse=True):
        rest = name[len(prefix):]
        if name.startswith(prefix) and rest in NAMES and UNITS[NAMES[rest]]["prefixable"]:
            return NAMES[rest], prefix
    return None, None


def normalise(text):
    out, previous_super = [], False
    for ch in text:
        if ch in SUPERSCRIPTS:
            if not previous_super:
                out.append("^")
            out.append(SUPERSCRIPTS[ch])
            previous_super = True
        else:
            out.append(ch)
            previous_super = False
    return "".join(out)


def tokenize(text):
    text = normalise(text)
    tokens, pos = [], 0
    while pos < len(text):
        if text[pos:].strip() == "":
            break
        m = TOKEN.match(text, pos)
        if not m or m.end() == pos:
            fail(f"cannot read the unit at '{text[pos:].strip()}'")
        if m.group("name"):
            tokens.append(("name", m.group("name")))
            if m.group("attached"):
                tokens.append(("op", "^"))
                tokens.append(("int", int(m.group("attached"))))
        elif m.group("number"):
            tokens.append(("int", int(m.group("number"))))
        else:
            tokens.append(("op", m.group("op")))
        pos = m.end()
    return tokens


class Parser:
    def __init__(self, text):
        self.text = text
        self.tokens = tokenize(text)
        self.i = 0
        self.names = []

    def peek(self):
        return self.tokens[self.i] if self.i < len(self.tokens) else (None, None)

    def take(self):
        tok = self.peek()
        self.i += 1
        return tok

    def parse(self):
        if not self.tokens:
            fail("empty unit")
        q = self.expr()
        if self.i != len(self.tokens):
            fail(f"unexpected '{self.peek()[1]}' in unit '{self.text}'")
        return q

    def expr(self):
        q = self.term()
        while True:
            kind, val = self.peek()
            if kind == "op" and val in ("*", "·", "×", "."):
                self.take()
                q = q.mul(self.term())
            elif kind == "op" and val == "/":
                self.take()
                q = q.mul(self.term(), -1)
            elif kind == "name" or (kind == "op" and val == "("):
                q = q.mul(self.term())  # juxtaposition: "N m"
            else:
                return q

    def term(self):
        q = self.atom()
        kind, val = self.peek()
        if kind == "op" and val in ("^", "**"):
            self.take()
            sign = 1
            kind, val = self.peek()
            if kind == "op" and val in ("-", "+"):
                self.take()
                sign = -1 if val == "-" else 1
            kind, val = self.take()
            if kind != "int":
                fail(f"an exponent must be an integer in '{self.text}'")
            if val > 12:
                fail("exponents above 12 are not supported")
            q = q.power(sign * val)
        return q

    def atom(self):
        kind, val = self.take()
        if kind == "name":
            symbol, prefix = resolve(val)
            if symbol is None:
                fail(f"unknown unit '{val}' (list_units shows the table)")
            self.names.append((symbol, prefix))
            return unit_quantity(symbol, prefix)
        if kind == "int":
            if val == 0:
                fail(f"a numeric factor in '{self.text}' must not be zero")
            return Quantity(Fraction(val))
        if kind == "op" and val == "(":
            q = self.expr()
            kind, val = self.take()
            if (kind, val) != ("op", ")"):
                fail(f"missing ')' in '{self.text}'")
            return q
        fail(f"unexpected '{val}' in unit '{self.text}'")


def dim_string(dims):
    parts = [d if e == 1 else f"{d}^{e}" for d, e in zip(DIMS, dims) if e]
    return " ".join(parts) if parts else "1 (dimensionless)"


def show(d):
    d = d.normalize()
    return format(d, "f") if -7 < d.adjusted() < 21 else str(d)


def decimal_string(fr, precision):
    """(text, rounded?) exact when the decimal expansion terminates within 60 digits."""
    den = fr.denominator
    twos = fives = 0
    while den % 2 == 0:
        den //= 2
        twos += 1
    while den % 5 == 0:
        den //= 5
        fives += 1
    with localcontext() as ctx:
        if den == 1 and max(twos, fives) <= 60 and len(str(abs(fr.numerator))) <= 60:
            ctx.prec = 200
            d = Decimal(fr.numerator) / Decimal(fr.denominator)
            if len(d.normalize().as_tuple().digits) <= 60:
                return show(d), False
        ctx.prec = precision
        d = Decimal(fr.numerator) / Decimal(fr.denominator)
        return show(d), True


def bare_temperature(parser):
    return len(parser.tokens) == 1 and parser.names and parser.names[0][0] in TEMPERATURE_SYMBOLS and parser.names[0][1] is None


def main():
    try:
        request = json.load(sys.stdin)
    except ValueError as error:
        fail(f"stdin is not JSON: {error}")
    if not isinstance(request, dict):
        fail("input must be a JSON object")
    if request.get("list_units"):
        print(json.dumps({"units": {s: {"definition": u["definition"], "aliases": u["aliases"], "prefixable": u["prefixable"],
                                         "dimension": dim_string(u["dimension"]), "source": u["source"]} for s, u in UNITS.items()},
                          "prefixes": TABLE["prefixes"], "generated": TABLE["generated"], "source": TABLE["source"]}, ensure_ascii=False))
        return
    value = request.get("value", 1)
    if isinstance(value, bool) or not isinstance(value, (int, float, str)):
        fail("value must be a number or a decimal string")
    try:
        x = Fraction(str(value).strip())
    except (ValueError, ZeroDivisionError):
        fail("value must be a number or a decimal string")
    source, target = request.get("from"), request.get("to")
    if not isinstance(source, str) or not source.strip():
        fail("from must be a unit expression")
    if target is not None and (not isinstance(target, str) or not target.strip()):
        fail("to must be a unit expression")
    mode = request.get("temperature", "absolute")
    if mode not in ("absolute", "difference"):
        fail("temperature must be absolute or difference")
    precision = request.get("precision", 15)
    if isinstance(precision, bool) or not isinstance(precision, int) or not 1 <= precision <= 50:
        fail("precision must be an integer from 1 to 50")
    p_from = Parser(source)
    q_from = p_from.parse()
    if target is None:
        q_to, p_to = Quantity(Fraction(1), list(q_from.dims)), None
        target_text = dim_string(q_from.dims) if any(q_from.dims) else "1"
    else:
        p_to = Parser(target)
        q_to = p_to.parse()
        target_text = target
    if q_from.dims != q_to.dims:
        fail(f"incompatible units: {source} is {dim_string(q_from.dims)}, {target_text} is {dim_string(q_to.dims)}")
    out = {"value": str(value), "from": source, "to": target_text, "dimension": dim_string(q_from.dims)}
    affine = mode == "absolute" and p_to is not None and bare_temperature(p_from) and bare_temperature(p_to)
    if affine:
        t_from, t_to = TABLE["temperatures"][p_from.names[0][0]], TABLE["temperatures"][p_to.names[0][0]]
        kelvin = (x + Fraction(t_from["offset"])) * Fraction(t_from["scale"])
        result = kelvin / Fraction(t_to["scale"]) - Fraction(t_to["offset"])
        out["temperature"] = "absolute: T/K = (t + offset) x scale on each side"
        if kelvin < 0:
            out["warning"] = "below absolute zero"
    else:
        factor = q_from.factor / q_to.factor
        result = x * factor
        text, rounded = decimal_string(factor, max(precision, 15))
        out["factor"] = str(factor) if q_from.exact and q_to.exact else None
        out["factor_decimal"] = text
        if any(s in TEMPERATURE_SYMBOLS - {"K"} for s, _ in p_from.names + (p_to.names if p_to else [])):
            out["temperature"] = "difference: degree units are read as temperature intervals here"
    exact = q_from.exact and q_to.exact
    text, rounded = decimal_string(result, precision)
    out["result"] = text
    out["result_exact"] = str(result) if exact else None
    out["result_is_rounded"] = rounded or not exact
    if not exact:
        out["note"] = "an angle unit in degrees is irrational in radians: the result is a 50-digit approximation"
    out["table"] = {"generated": TABLE["generated"], "source": TABLE["source"]}
    print(json.dumps(out, ensure_ascii=False))


if __name__ == "__main__":
    try:
        main()
    except (ArithmeticError, ValueError, RecursionError) as error:
        fail(f"the numbers are out of the range this tool can compute: {type(error).__name__}")
units.json · sha256 24336a28d3b3aa1f70de9e230426f3b3bbcc960c5eac6d0a77dac9f8b9ec0c66
{
 "generated": "2026-10-18",
 "generator": "tools/catalog/data/unit_convert.py",
 "source": "BIPM SI Brochure, 9th ed. (2019, v2.01 2022); NIST SP 811 (2008) App. B; NIST Handbook 44 App. C; IAU 2012 B2",
 "dimensions": [
  "m",
  "kg",
  "s",
  "A",
  "K",
  "mol",
  "cd"
 ],
 "prefixes": {
  "Q": "1000000000000000000000000000000",
  "R": "1000000000000000000000000000",
  "Y": "1000000000000000000000000",
  "Z": "1000000000000000000000",
  "E": "1000000000000000000",
  "P": "1000000000000000",
  "T": "1000000000000",
  "G": "1000000000",
  "M": "1000000",
  "k": "1000",
  "h": "100",
  "da": "10",
  "d": "1/10",
  "c": "1/100",
  "m": "1/1000",
  "µ": "1/1000000",
  "μ": "1/1000000",
  "u": "1/1000000",
  "n": "1/1000000000",
  "p": "1/1000000000000",
  "f": "1/1000000000000000",
  "a": "1/1000000000000000000",
  "z": "1/1000000000000000000000",
  "y": "1/1000000000000000000000000",
  "r": "1/1000000000000000000000000000",
  "q": "1/1000000000000000000000000000000"
 },
 "temperatures": {
  "K": {
   "scale": "1",
   "offset": "0"
  },
  "degC": {
   "scale": "1",
   "offset": "273.15"
  },
  "degF": {
   "scale": "5/9",
   "offset": "459.67"
  },
  "degR": {
   "scale": "5/9",
   "offset": "0"
  }
 },
 "units": {
  "m": {
   "factor": "1",
   "dimension": [
    1,
    0,
    0,
    0,
    0,
    0,
    0
   ],
   "prefixable": true,
   "aliases": [
    "metre",
    "meter"
   ],
   "definition": "SI base unit of length",
   "source": "SI Brochure 9th ed. (2019), Table 2",
   "exact": true
  },
  "g": {
   "factor": "1/1000",
   "dimension": [
    0,
    1,
    0,
    0,
    0,
    0,
    0
   ],
   "prefixable": true,
   "aliases": [
    "gram"
   ],
   "definition": "1/1000 kg (the kilogram is the SI base unit; prefixes attach to the gram)",
   "source": "SI Brochure 9th ed. (2019), Table 2",
   "exact": true
  },
  "s": {
   "factor": "1",
   "dimension": [
    0,
    0,
    1,
    0,
    0,
    0,
    0
   ],
   "prefixable": true,
   "aliases": [
    "second"
   ],
   "definition": "SI base unit of time",
   "source": "SI Brochure 9th ed. (2019), Table 2",
   "exact": true
  },
  "A": {
   "factor": "1",
   "dimension": [
    0,
    0,
    0,
    1,
    0,
    0,
    0
   ],
   "prefixable": true,
   "aliases": [
    "ampere"
   ],
   "definition": "SI base unit of electric current",
   "source": "SI Brochure 9th ed. (2019), Table 2",
   "exact": true
  },
  "K": {
   "factor": "1",
   "dimension": [
    0,
    0,
    0,
    0,
    1,
    0,
    0
   ],
   "prefixable": true,
   "aliases": [
    "kelvin"
   ],
   "definition": "SI base unit of thermodynamic temperature",
   "source": "SI Brochure 9th ed. (2019), Table 2",
   "exact": true
  },
  "mol": {
   "factor": "1",
   "dimension": [
    0,
    0,
    0,
    0,
    0,
    1,
    0
   ],
   "prefixable": true,
   "aliases": [
    "mole"
   ],
   "definition": "SI base unit of amount of substance",
   "source": "SI Brochure 9th ed. (2019), Table 2",
   "exact": true
  },
  "cd": {
   "factor": "1",
   "dimension": [
    0,
    0,
    0,
    0,
    0,
    0,
    1
   ],
   "prefixable": true,
   "aliases": [
    "candela"
   ],
   "definition": "SI base unit of luminous intensity",
   "source": "SI Brochure 9th ed. (2019), Table 2",
   "exact": true
  },
  "rad": {
   "factor": "1",
   "dimension": [
    0,
    0,
    0,
    0,
    0,
    0,
    0
   ],
   "prefixable": true,
   "aliases": [
    "radian"
   ],
   "definition": "m/m",
   "source": "SI Brochure 9th ed. (2019), Table 4",
   "exact": true
  },
  "sr": {
   "factor": "1",
   "dimension": [
    0,
    0,
    0,
    0,
    0,
    0,
    0
   ],
   "prefixable": true,
   "aliases": [
    "steradian"
   ],
   "definition": "m2/m2",
   "source": "SI Brochure 9th ed. (2019), Table 4",
   "exact": true
  },
  "Hz": {
   "factor": "1",
   "dimension": [
    0,
    0,
    -1,
    0,
    0,
    0,
    0
   ],
   "prefixable": true,
   "aliases": [
    "hertz"
   ],
   "definition": "s-1",
   "source": "SI Brochure 9th ed. (2019), Table 4",
   "exact": true
  },
  "N": {
   "factor": "1",
   "dimension": [
    1,
    1,
    -2,
    0,
    0,
    0,
    0
   ],
   "prefixable": true,
   "aliases": [
    "newton"
   ],
   "definition": "kg m s-2",
   "source": "SI Brochure 9th ed. (2019), Table 4",
   "exact": true
  },
  "Pa": {
   "factor": "1",
   "dimension": [
    -1,
    1,
    -2,
    0,
    0,
    0,
    0
   ],
   "prefixable": true,
   "aliases": [
    "pascal"
   ],
   "definition": "N/m2",
   "source": "SI Brochure 9th ed. (2019), Table 4",
   "exact": true
  },
  "J": {
   "factor": "1",
   "dimension": [
    2,
    1,
    -2,
    0,
    0,
    0,
    0
   ],
   "prefixable": true,
   "aliases": [
    "joule"
   ],
   "definition": "N m",
   "source": "SI Brochure 9th ed. (2019), Table 4",
   "exact": true
  },
  "W": {
   "factor": "1",
   "dimension": [
    2,
    1,
    -3,
    0,
    0,
    0,
    0
   ],
   "prefixable": true,
   "aliases": [
    "watt"
   ],
   "definition": "J/s",
   "source": "SI Brochure 9th ed. (2019), Table 4",
   "exact": true
  },
  "C": {
   "factor": "1",
   "dimension": [
    0,
    0,
    1,
    1,
    0,
    0,
    0
   ],
   "prefixable": true,
   "aliases": [
    "coulomb"
   ],
   "definition": "A s",
   "source": "SI Brochure 9th ed. (2019), Table 4",
   "exact": true
  },
  "V": {
   "factor": "1",
   "dimension": [
    2,
    1,
    -3,
    -1,
    0,
    0,
    0
   ],
   "prefixable": true,
   "aliases": [
    "volt"
   ],
   "definition": "W/A",
   "source": "SI Brochure 9th ed. (2019), Table 4",
   "exact": true
  },
  "F": {
   "factor": "1",
   "dimension": [
    -2,
    -1,
    4,
    2,
    0,
    0,
    0
   ],
   "prefixable": true,
   "aliases": [
    "farad"
   ],
   "definition": "C/V",
   "source": "SI Brochure 9th ed. (2019), Table 4",
   "exact": true
  },
  "ohm": {
   "factor": "1",
   "dimension": [
    2,
    1,
    -3,
    -2,
    0,
    0,
    0
   ],
   "prefixable": true,
   "aliases": [
    "Ω",
    "Ω"
   ],
   "definition": "V/A",
   "source": "SI Brochure 9th ed. (2019), Table 4",
   "exact": true
  },
  "S": {
   "factor": "1",
   "dimension": [
    -2,
    -1,
    3,
    2,
    0,
    0,
    0
   ],
   "prefixable": true,
   "aliases": [
    "siemens"
   ],
   "definition": "A/V",
   "source": "SI Brochure 9th ed. (2019), Table 4",
   "exact": true
  },
  "Wb": {
   "factor": "1",
   "dimension": [
    2,
    1,
    -2,
    -1,
    0,
    0,
    0
   ],
   "prefixable": true,
   "aliases": [
    "weber"
   ],
   "definition": "V s",
   "source": "SI Brochure 9th ed. (2019), Table 4",
   "exact": true
  },
  "T": {
   "factor": "1",
   "dimension": [
    0,
    1,
    -2,
    -1,
    0,
    0,
    0
   ],
   "prefixable": true,
   "aliases": [
    "tesla"
   ],
   "definition": "Wb/m2",
   "source": "SI Brochure 9th ed. (2019), Table 4",
   "exact": true
  },
  "H": {
   "factor": "1",
   "dimension": [
    2,
    1,
    -2,
    -2,
    0,
    0,
    0
   ],
   "prefixable": true,
   "aliases": [
    "henry"
   ],
   "definition": "Wb/A",
   "source": "SI Brochure 9th ed. (2019), Table 4",
   "exact": true
  },
  "lm": {
   "factor": "1",
   "dimension": [
    0,
    0,
    0,
    0,
    0,
    0,
    1
   ],
   "prefixable": true,
   "aliases": [
    "lumen"
   ],
   "definition": "cd sr",
   "source": "SI Brochure 9th ed. (2019), Table 4",
   "exact": true
  },
  "lx": {
   "factor": "1",
   "dimension": [
    -2,
    0,
    0,
    0,
    0,
    0,
    1
   ],
   "prefixable": true,
   "aliases": [
    "lux"
   ],
   "definition": "lm/m2",
   "source": "SI Brochure 9th ed. (2019), Table 4",
   "exact": true
  },
  "Bq": {
   "factor": "1",
   "dimension": [
    0,
    0,
    -1,
    0,
    0,
    0,
    0
   ],
   "prefixable": true,
   "aliases": [
    "becquerel"
   ],
   "definition": "s-1",
   "source": "SI Brochure 9th ed. (2019), Table 4",
   "exact": true
  },
  "Gy": {
   "factor": "1",
   "dimension": [
    2,
    0,
    -2,
    0,
    0,
    0,
    0
   ],
   "prefixable": true,
   "aliases": [
    "gray"
   ],
   "definition": "J/kg",
   "source": "SI Brochure 9th ed. (2019), Table 4",
   "exact": true
  },
  "Sv": {
   "factor": "1",
   "dimension": [
    2,
    0,
    -2,
    0,
    0,
    0,
    0
   ],
   "prefixable": true,
   "aliases": [
    "sievert"
   ],
   "definition": "J/kg",
   "source": "SI Brochure 9th ed. (2019), Table 4",
   "exact": true
  },
  "kat": {
   "factor": "1",
   "dimension": [
    0,
    0,
    -1,
    0,
    0,
    1,
    0
   ],
   "prefixable": true,
   "aliases": [
    "katal"
   ],
   "definition": "mol/s",
   "source": "SI Brochure 9th ed. (2019), Table 4",
   "exact": true
  },
  "degC": {
   "factor": "1",
   "dimension": [
    0,
    0,
    0,
    0,
    1,
    0,
    0
   ],
   "prefixable": false,
   "aliases": [
    "°C",
    "celsius"
   ],
   "definition": "degree Celsius: t/°C = T/K - 273.15; as a difference 1 °C = 1 K",
   "source": "SI Brochure 9th ed. (2019), Table 4",
   "exact": true
  },
  "min": {
   "factor": "60",
   "dimension": [
    0,
    0,
    1,
    0,
    0,
    0,
    0
   ],
   "prefixable": false,
   "aliases": [
    "minute"
   ],
   "definition": "60 s",
   "source": "SI Brochure 9th ed. (2019), Table 8",
   "exact": true
  },
  "h": {
   "factor": "3600",
   "dimension": [
    0,
    0,
    1,
    0,
    0,
    0,
    0
   ],
   "prefixable": false,
   "aliases": [
    "hour"
   ],
   "definition": "60 min = 3600 s",
   "source": "SI Brochure 9th ed. (2019), Table 8",
   "exact": true
  },
  "d": {
   "factor": "86400",
   "dimension": [
    0,
    0,
    1,
    0,
    0,
    0,
    0
   ],
   "prefixable": false,
   "aliases": [
    "day"
   ],
   "definition": "24 h = 86400 s",
   "source": "SI Brochure 9th ed. (2019), Table 8",
   "exact": true
  },
  "wk": {
   "factor": "604800",
   "dimension": [
    0,
    0,
    1,
    0,
    0,
    0,
    0
   ],
   "prefixable": false,
   "aliases": [
    "week"
   ],
   "definition": "7 d",
   "source": "7 days of 86400 s",
   "exact": true
  },
  "yr": {
   "factor": "31557600",
   "dimension": [
    0,
    0,
    1,
    0,
    0,
    0,
    0
   ],
   "prefixable": false,
   "aliases": [
    "year",
    "a"
   ],
   "definition": "Julian year, 365.25 d",
   "source": "IAU; NIST SP 811 (2008), App. B.9 (year, 365.25 d)",
   "exact": true
  },
  "au": {
   "factor": "149597870700",
   "dimension": [
    1,
    0,
    0,
    0,
    0,
    0,
    0
   ],
   "prefixable": false,
   "aliases": [
    "astronomical_unit"
   ],
   "definition": "149 597 870 700 m",
   "source": "SI Brochure 9th ed. (2019), Table 8 (IAU 2012 Resolution B2)",
   "exact": true
  },
  "ly": {
   "factor": "9460730472580800",
   "dimension": [
    1,
    0,
    0,
    0,
    0,
    0,
    0
   ],
   "prefixable": false,
   "aliases": [
    "light_year"
   ],
   "definition": "c x Julian year = 9 460 730 472 580 800 m",
   "source": "IAU; c from SI Brochure 9th ed. (2019)",
   "exact": true
  },
  "ha": {
   "factor": "10000",
   "dimension": [
    2,
    0,
    0,
    0,
    0,
    0,
    0
   ],
   "prefixable": false,
   "aliases": [
    "hectare"
   ],
   "definition": "1 hm2 = 10^4 m2",
   "source": "SI Brochure 9th ed. (2019), Table 8",
   "exact": true
  },
  "L": {
   "factor": "1/1000",
   "dimension": [
    3,
    0,
    0,
    0,
    0,
    0,
    0
   ],
   "prefixable": true,
   "aliases": [
    "l",
    "litre",
    "liter"
   ],
   "definition": "1 dm3 = 10^-3 m3",
   "source": "SI Brochure 9th ed. (2019), Table 8",
   "exact": true
  },
  "t": {
   "factor": "1000",
   "dimension": [
    0,
    1,
    0,
    0,
    0,
    0,
    0
   ],
   "prefixable": true,
   "aliases": [
    "tonne"
   ],
   "definition": "10^3 kg",
   "source": "SI Brochure 9th ed. (2019), Table 8",
   "exact": true
  },
  "eV": {
   "factor": "801088317/5000000000000000000000000000",
   "dimension": [
    2,
    1,
    -2,
    0,
    0,
    0,
    0
   ],
   "prefixable": true,
   "aliases": [
    "electronvolt"
   ],
   "definition": "e x 1 V, e = 1.602176634e-19 C exactly",
   "source": "SI Brochure 9th ed. (2019), Table 8",
   "exact": true
  },
  "angstrom": {
   "factor": "1/10000000000",
   "dimension": [
    1,
    0,
    0,
    0,
    0,
    0,
    0
   ],
   "prefixable": false,
   "aliases": [
    "Å",
    "Å"
   ],
   "definition": "10^-10 m",
   "source": "NIST SP 811 (2008), App. B.8",
   "exact": true
  },
  "bar": {
   "factor": "100000",
   "dimension": [
    -1,
    1,
    -2,
    0,
    0,
    0,
    0
   ],
   "prefixable": true,
   "aliases": [],
   "definition": "10^5 Pa",
   "source": "NIST SP 811 (2008), App. B.8",
   "exact": true
  },
  "in": {
   "factor": "127/5000",
   "dimension": [
    1,
    0,
    0,
    0,
    0,
    0,
    0
   ],
   "prefixable": false,
   "aliases": [
    "inch"
   ],
   "definition": "0.0254 m exactly",
   "source": "NIST SP 811 (2008), App. B.8; 1959 agreement",
   "exact": true
  },
  "ft": {
   "factor": "381/1250",
   "dimension": [
    1,
    0,
    0,
    0,
    0,
    0,
    0
   ],
   "prefixable": false,
   "aliases": [
    "foot",
    "feet"
   ],
   "definition": "12 in = 0.3048 m exactly",
   "source": "NIST SP 811 (2008), App. B.8",
   "exact": true
  },
  "yd": {
   "factor": "1143/1250",
   "dimension": [
    1,
    0,
    0,
    0,
    0,
    0,
    0
   ],
   "prefixable": false,
   "aliases": [
    "yard"
   ],
   "definition": "3 ft = 0.9144 m exactly",
   "source": "NIST SP 811 (2008), App. B.8; 1959 agreement",
   "exact": true
  },
  "mi": {
   "factor": "201168/125",
   "dimension": [
    1,
    0,
    0,
    0,
    0,
    0,
    0
   ],
   "prefixable": false,
   "aliases": [
    "mile"
   ],
   "definition": "5280 ft = 1609.344 m exactly (international mile)",
   "source": "NIST SP 811 (2008), App. B.8",
   "exact": true
  },
  "nmi": {
   "factor": "1852",
   "dimension": [
    1,
    0,
    0,
    0,
    0,
    0,
    0
   ],
   "prefixable": false,
   "aliases": [
    "nautical_mile"
   ],
   "definition": "1852 m exactly",
   "source": "NIST SP 811 (2008), App. B.8; SI Brochure 9th ed. (2019), Table 8 note",
   "exact": true
  },
  "acre": {
   "factor": "316160658/78125",
   "dimension": [
    2,
    0,
    0,
    0,
    0,
    0,
    0
   ],
   "prefixable": false,
   "aliases": [],
   "definition": "43 560 ft2 = 4046.8564224 m2 (international foot)",
   "source": "NIST SP 811 (2008), App. B.8",
   "exact": true
  },
  "lb": {
   "factor": "45359237/100000000",
   "dimension": [
    0,
    1,
    0,
    0,
    0,
    0,
    0
   ],
   "prefixable": false,
   "aliases": [
    "pound"
   ],
   "definition": "avoirdupois pound, 0.45359237 kg exactly",
   "source": "NIST SP 811 (2008), App. B.8; 1959 agreement",
   "exact": true
  },
  "oz": {
   "factor": "45359237/1600000000",
   "dimension": [
    0,
    1,
    0,
    0,
    0,
    0,
    0
   ],
   "prefixable": false,
   "aliases": [
    "ounce"
   ],
   "definition": "lb/16 = 28.349523125 g",
   "source": "NIST SP 811 (2008), App. B.8",
   "exact": true
  },
  "st": {
   "factor": "317514659/50000000",
   "dimension": [
    0,
    1,
    0,
    0,
    0,
    0,
    0
   ],
   "prefixable": false,
   "aliases": [
    "stone"
   ],
   "definition": "14 lb",
   "source": "NIST Handbook 44 (2024), App. C",
   "exact": true
  },
  "ton": {
   "factor": "45359237/50000",
   "dimension": [
    0,
    1,
    0,
    0,
    0,
    0,
    0
   ],
   "prefixable": false,
   "aliases": [
    "short_ton"
   ],
   "definition": "short ton, 2000 lb",
   "source": "NIST SP 811 (2008), App. B.8",
   "exact": true
  },
  "long_ton": {
   "factor": "317514659/312500",
   "dimension": [
    0,
    1,
    0,
    0,
    0,
    0,
    0
   ],
   "prefixable": false,
   "aliases": [],
   "definition": "long ton, 2240 lb",
   "source": "NIST SP 811 (2008), App. B.8",
   "exact": true
  },
  "gr": {
   "factor": "6479891/100000000000",
   "dimension": [
    0,
    1,
    0,
    0,
    0,
    0,
    0
   ],
   "prefixable": false,
   "aliases": [
    "grain"
   ],
   "definition": "lb/7000 = 64.79891 mg",
   "source": "NIST SP 811 (2008), App. B.8",
   "exact": true
  },
  "ozt": {
   "factor": "19439673/625000000",
   "dimension": [
    0,
    1,
    0,
    0,
    0,
    0,
    0
   ],
   "prefixable": false,
   "aliases": [
    "troy_ounce"
   ],
   "definition": "480 gr = 31.1034768 g",
   "source": "NIST SP 811 (2008), App. B.8",
   "exact": true
  },
  "ct": {
   "factor": "1/5000",
   "dimension": [
    0,
    1,
    0,
    0,
    0,
    0,
    0
   ],
   "prefixable": false,
   "aliases": [
    "carat"
   ],
   "definition": "metric carat, 200 mg",
   "source": "NIST SP 811 (2008), App. B.8",
   "exact": true
  },
  "gal": {
   "factor": "473176473/125000000000",
   "dimension": [
    3,
    0,
    0,
    0,
    0,
    0,
    0
   ],
   "prefixable": false,
   "aliases": [
    "gallon",
    "us_gal"
   ],
   "definition": "US gallon, 231 in3 = 3.785411784 L",
   "source": "NIST SP 811 (2008), App. B.8; NIST Handbook 44 (2024), App. C",
   "exact": true
  },
  "qt": {
   "factor": "473176473/500000000000",
   "dimension": [
    3,
    0,
    0,
    0,
    0,
    0,
    0
   ],
   "prefixable": false,
   "aliases": [
    "quart"
   ],
   "definition": "US liquid quart, gal/4",
   "source": "NIST Handbook 44 (2024), App. C",
   "exact": true
  },
  "pt": {
   "factor": "473176473/1000000000000",
   "dimension": [
    3,
    0,
    0,
    0,
    0,
    0,
    0
   ],
   "prefixable": false,
   "aliases": [
    "pint"
   ],
   "definition": "US liquid pint, gal/8",
   "source": "NIST Handbook 44 (2024), App. C",
   "exact": true
  },
  "cup": {
   "factor": "473176473/2000000000000",
   "dimension": [
    3,
    0,
    0,
    0,
    0,
    0,
    0
   ],
   "prefixable": false,
   "aliases": [],
   "definition": "US cup, gal/16 = 8 fl oz",
   "source": "NIST Handbook 44 (2024), App. C",
   "exact": true
  },
  "floz": {
   "factor": "473176473/16000000000000",
   "dimension": [
    3,
    0,
    0,
    0,
    0,
    0,
    0
   ],
   "prefixable": false,
   "aliases": [
    "fl_oz",
    "fluid_ounce"
   ],
   "definition": "US fluid ounce, gal/128",
   "source": "NIST SP 811 (2008), App. B.8; NIST Handbook 44 (2024), App. C",
   "exact": true
  },
  "tbsp": {
   "factor": "473176473/32000000000000",
   "dimension": [
    3,
    0,
    0,
    0,
    0,
    0,
    0
   ],
   "prefixable": false,
   "aliases": [
    "tablespoon"
   ],
   "definition": "US tablespoon, fl oz/2",
   "source": "NIST Handbook 44 (2024), App. C",
   "exact": true
  },
  "tsp": {
   "factor": "157725491/32000000000000",
   "dimension": [
    3,
    0,
    0,
    0,
    0,
    0,
    0
   ],
   "prefixable": false,
   "aliases": [
    "teaspoon"
   ],
   "definition": "US teaspoon, fl oz/6",
   "source": "NIST Handbook 44 (2024), App. C",
   "exact": true
  },
  "bbl": {
   "factor": "9936705933/62500000000",
   "dimension": [
    3,
    0,
    0,
    0,
    0,
    0,
    0
   ],
   "prefixable": false,
   "aliases": [
    "barrel"
   ],
   "definition": "oil barrel, 42 US gal",
   "source": "NIST SP 811 (2008), App. B.8",
   "exact": true
  },
  "imp_gal": {
   "factor": "454609/100000000",
   "dimension": [
    3,
    0,
    0,
    0,
    0,
    0,
    0
   ],
   "prefixable": false,
   "aliases": [
    "imperial_gallon"
   ],
   "definition": "imperial gallon, 4.54609 L exactly",
   "source": "NIST SP 811 (2008), App. B.8",
   "exact": true
  },
  "imp_pt": {
   "factor": "454609/800000000",
   "dimension": [
    3,
    0,
    0,
    0,
    0,
    0,
    0
   ],
   "prefixable": false,
   "aliases": [
    "imperial_pint"
   ],
   "definition": "imperial pint, imp gal/8",
   "source": "NIST SP 811 (2008), App. B.8",
   "exact": true
  },
  "imp_floz": {
   "factor": "454609/16000000000",
   "dimension": [
    3,
    0,
    0,
    0,
    0,
    0,
    0
   ],
   "prefixable": false,
   "aliases": [
    "imperial_fluid_ounce"
   ],
   "definition": "imperial fluid ounce, imp gal/160",
   "source": "NIST SP 811 (2008), App. B.8",
   "exact": true
  },
  "gn": {
   "factor": "196133/20000",
   "dimension": [
    1,
    0,
    -2,
    0,
    0,
    0,
    0
   ],
   "prefixable": false,
   "aliases": [
    "g0",
    "standard_gravity"
   ],
   "definition": "standard acceleration of gravity, 9.80665 m/s2",
   "source": "NIST SP 811 (2008), App. B.8",
   "exact": true
  },
  "lbf": {
   "factor": "8896443230521/2000000000000",
   "dimension": [
    1,
    1,
    -2,
    0,
    0,
    0,
    0
   ],
   "prefixable": false,
   "aliases": [
    "pound_force"
   ],
   "definition": "lb x g_n = 4.4482216152605 N",
   "source": "NIST SP 811 (2008), App. B.8",
   "exact": true
  },
  "kgf": {
   "factor": "196133/20000",
   "dimension": [
    1,
    1,
    -2,
    0,
    0,
    0,
    0
   ],
   "prefixable": false,
   "aliases": [
    "kilogram_force"
   ],
   "definition": "kg x g_n = 9.80665 N",
   "source": "NIST SP 811 (2008), App. B.8",
   "exact": true
  },
  "psi": {
   "factor": "8896443230521/1290320000",
   "dimension": [
    -1,
    1,
    -2,
    0,
    0,
    0,
    0
   ],
   "prefixable": false,
   "aliases": [],
   "definition": "lbf/in2",
   "source": "NIST SP 811 (2008), App. B.8",
   "exact": true
  },
  "atm": {
   "factor": "101325",
   "dimension": [
    -1,
    1,
    -2,
    0,
    0,
    0,
    0
   ],
   "prefixable": false,
   "aliases": [
    "atmosphere"
   ],
   "definition": "standard atmosphere, 101 325 Pa",
   "source": "NIST SP 811 (2008), App. B.8",
   "exact": true
  },
  "torr": {
   "factor": "20265/152",
   "dimension": [
    -1,
    1,
    -2,
    0,
    0,
    0,
    0
   ],
   "prefixable": false,
   "aliases": [
    "Torr"
   ],
   "definition": "atm/760",
   "source": "NIST SP 811 (2008), App. B.8",
   "exact": true
  },
  "mmHg": {
   "factor": "26664477483/200000000",
   "dimension": [
    -1,
    1,
    -2,
    0,
    0,
    0,
    0
   ],
   "prefixable": false,
   "aliases": [],
   "definition": "conventional millimetre of mercury, 133.322387415 Pa",
   "source": "NIST SP 811 (2008), App. B.8",
   "exact": true
  },
  "inHg": {
   "factor": "3386388640341/1000000000",
   "dimension": [
    -1,
    1,
    -2,
    0,
    0,
    0,
    0
   ],
   "prefixable": false,
   "aliases": [],
   "definition": "conventional inch of mercury, 25.4 mmHg",
   "source": "NIST SP 811 (2008), App. B.8",
   "exact": true
  },
  "cal": {
   "factor": "523/125",
   "dimension": [
    2,
    1,
    -2,
    0,
    0,
    0,
    0
   ],
   "prefixable": true,
   "aliases": [
    "calorie"
   ],
   "definition": "thermochemical calorie, 4.184 J exactly",
   "source": "NIST SP 811 (2008), App. B.8",
   "exact": true
  },
  "cal_IT": {
   "factor": "10467/2500",
   "dimension": [
    2,
    1,
    -2,
    0,
    0,
    0,
    0
   ],
   "prefixable": true,
   "aliases": [],
   "definition": "International Table calorie, 4.1868 J exactly",
   "source": "NIST SP 811 (2008), App. B.8",
   "exact": true
  },
  "Btu": {
   "factor": "52752792631/50000000",
   "dimension": [
    2,
    1,
    -2,
    0,
    0,
    0,
    0
   ],
   "prefixable": false,
   "aliases": [
    "BTU",
    "btu"
   ],
   "definition": "International Table Btu, 1055.05585262 J",
   "source": "NIST SP 811 (2008), App. B.8",
   "exact": true
  },
  "Wh": {
   "factor": "3600",
   "dimension": [
    2,
    1,
    -2,
    0,
    0,
    0,
    0
   ],
   "prefixable": true,
   "aliases": [
    "watt_hour"
   ],
   "definition": "W x h = 3600 J",
   "source": "SI Brochure 9th ed. (2019), Table 8 (hour)",
   "exact": true
  },
  "Ah": {
   "factor": "3600",
   "dimension": [
    0,
    0,
    1,
    1,
    0,
    0,
    0
   ],
   "prefixable": true,
   "aliases": [
    "amp_hour"
   ],
   "definition": "A x h = 3600 C",
   "source": "SI Brochure 9th ed. (2019), Table 8 (hour)",
   "exact": true
  },
  "hp": {
   "factor": "37284993579113511/50000000000000",
   "dimension": [
    2,
    1,
    -3,
    0,
    0,
    0,
    0
   ],
   "prefixable": false,
   "aliases": [
    "horsepower"
   ],
   "definition": "mechanical horsepower, 550 ft lbf/s = 745.69987158227022 W",
   "source": "NIST SP 811 (2008), App. B.8",
   "exact": true
  },
  "hp_metric": {
   "factor": "588399/800",
   "dimension": [
    2,
    1,
    -3,
    0,
    0,
    0,
    0
   ],
   "prefixable": false,
   "aliases": [
    "PS"
   ],
   "definition": "metric horsepower, 75 kgf m/s = 735.49875 W",
   "source": "NIST SP 811 (2008), App. B.8",
   "exact": true
  },
  "mph": {
   "factor": "1397/3125",
   "dimension": [
    1,
    0,
    -1,
    0,
    0,
    0,
    0
   ],
   "prefixable": false,
   "aliases": [],
   "definition": "mi/h",
   "source": "NIST SP 811 (2008), App. B.8",
   "exact": true
  },
  "kn": {
   "factor": "463/900",
   "dimension": [
    1,
    0,
    -1,
    0,
    0,
    0,
    0
   ],
   "prefixable": false,
   "aliases": [
    "knot"
   ],
   "definition": "nmi/h",
   "source": "NIST SP 811 (2008), App. B.8",
   "exact": true
  },
  "percent": {
   "factor": "1/100",
   "dimension": [
    0,
    0,
    0,
    0,
    0,
    0,
    0
   ],
   "prefixable": false,
   "aliases": [
    "%"
   ],
   "definition": "1/100",
   "source": "ISO 80000-1",
   "exact": true
  },
  "ppm": {
   "factor": "1/1000000",
   "dimension": [
    0,
    0,
    0,
    0,
    0,
    0,
    0
   ],
   "prefixable": false,
   "aliases": [],
   "definition": "10^-6",
   "source": "ISO 80000-1",
   "exact": true
  },
  "degF": {
   "factor": "5/9",
   "dimension": [
    0,
    0,
    0,
    0,
    1,
    0,
    0
   ],
   "prefixable": false,
   "aliases": [
    "°F",
    "fahrenheit"
   ],
   "definition": "degree Fahrenheit: T/K = (t/°F + 459.67) x 5/9; as a difference 5/9 K",
   "source": "NIST SP 811 (2008), App. B.8",
   "exact": true
  },
  "degR": {
   "factor": "5/9",
   "dimension": [
    0,
    0,
    0,
    0,
    1,
    0,
    0
   ],
   "prefixable": false,
   "aliases": [
    "°R",
    "rankine"
   ],
   "definition": "degree Rankine, 5/9 K",
   "source": "NIST SP 811 (2008), App. B.8",
   "exact": true
  },
  "deg": {
   "factor": null,
   "factor_decimal": "0.01745329251994329576923690768488612713442871888542",
   "dimension": [
    0,
    0,
    0,
    0,
    0,
    0,
    0
   ],
   "prefixable": false,
   "aliases": [
    "°",
    "degree"
   ],
   "definition": "pi/180 rad",
   "source": "SI Brochure 9th ed. (2019), Table 8",
   "exact": false
  },
  "arcmin": {
   "factor": null,
   "factor_decimal": "0.00029088820866572159615394846141476878557381198142",
   "dimension": [
    0,
    0,
    0,
    0,
    0,
    0,
    0
   ],
   "prefixable": false,
   "aliases": [
    "′"
   ],
   "definition": "(1/60) degree",
   "source": "SI Brochure 9th ed. (2019), Table 8",
   "exact": false
  },
  "arcsec": {
   "factor": null,
   "factor_decimal": "0.00000484813681109535993589914102357947975956353302",
   "dimension": [
    0,
    0,
    0,
    0,
    0,
    0,
    0
   ],
   "prefixable": false,
   "aliases": [
    "″"
   ],
   "definition": "(1/60) arcmin",
   "source": "SI Brochure 9th ed. (2019), Table 8",
   "exact": false
  }
 }
}

Where it is served

The registry serves only AXLR clients, so a browser gets 403; fetch with a User-Agent that starts with axlr:

curl -A axlr-docs/1.0 -O https://registry.underpassai.com/tools/unit_convert/1.0.0/manifest.json
curl -A axlr-docs/1.0 -O https://registry.underpassai.com/tools/unit_convert/1.0.0/unit_convert.py
curl -A axlr-docs/1.0 -O https://registry.underpassai.com/tools/unit_convert/1.0.0/units.json