UCUM — Unified Code for Units of Measure
The ucum package provides a Dart implementation of the UCUM standard, enabling unit validation, conversion, comparison, and arithmetic for healthcare and scientific measurements. Translated from the official Java reference implementation, it supports all 2000+ UCUM-defined units with arbitrary-precision decimal arithmetic.
Installation
dependencies:
ucum: ^0.7.0
Quick Start
import 'package:ucum/ucum.dart';
void main() {
final ucum = UcumService();
// Validate a unit string
final error = ucum.validate('mg/dL');
print(error); // null — unit is valid
// Convert 100 mg to grams
final result = ucum.convert(
UcumDecimal.fromString('100'),
'mg',
'g',
);
print(result.asUcumDecimal()); // 0.1
// Check if two units are comparable
print(ucum.isComparable('mg', 'g')); // true
print(ucum.isComparable('mg', 'mL')); // false
}
UcumService
UcumService is the main public API. It is a singleton — calling UcumService() always returns the same instance.
Validation
final ucum = UcumService();
// Returns null if the unit is valid, or an error message
final error = ucum.validate('kg');
print(error); // null
final bad = ucum.validate('not_a_unit');
print(bad); // "not_a_unit is not a valid UCUM unit"
Conversion
final ucum = UcumService();
// Convert a value from one unit to another
final grams = ucum.convert(
UcumDecimal.fromString('2.5'),
'kg',
'g',
);
print(grams.asUcumDecimal()); // 2500
// Convert temperature
final fahrenheit = ucum.convert(
UcumDecimal.fromString('37'),
'Cel',
'[degF]',
);
print(fahrenheit.asUcumDecimal()); // 98.6
Multiplication and Division
Multiply and divide quantities using Pair objects (value + unit):
final ucum = UcumService();
// Multiply: 5 m * 3 m = 15 m2
final area = ucum.multiply(
Pair(value: UcumDecimal.fromString('5'), unit: 'm'),
Pair(value: UcumDecimal.fromString('3'), unit: 'm'),
);
print('${area.value.asUcumDecimal()} ${area.unit}'); // 15 m2
// Divide: 100 mg / 2 dL = 50 mg/dL
final concentration = ucum.divideBy(
Pair(value: UcumDecimal.fromString('100'), unit: 'mg'),
Pair(value: UcumDecimal.fromString('2'), unit: 'dL'),
);
print('${concentration.value.asUcumDecimal()} ${concentration.unit}');
// 50 mg/dL
Analysis and Canonical Forms
final ucum = UcumService();
// Get canonical (base SI) units
print(ucum.getCanonicalUnits('km/h')); // m/s
// Analyse a unit string
print(ucum.analyse('mg/dL')); // g/L
// Get canonical form with converted value
final canonical = ucum.getCanonicalForm(
Pair(value: UcumDecimal.fromString('60'), unit: 'km/h'),
);
print('${canonical.value.asUcumDecimal()} ${canonical.unit}');
// 16.6666666666666666666667 m/s
Search
final ucum = UcumService();
// Search for units by name
final results = ucum.search(ConceptKind.unit, 'gram', false);
for (final concept in results) {
print('${concept.code} — ${concept.names.first}');
}
UcumDecimal
UcumDecimal provides arbitrary-precision decimal arithmetic, avoiding floating-point rounding errors that are critical to avoid in healthcare calculations. It tracks precision (significant figures) through all operations.
Constructors
// From a string (preserves precision from the string representation)
final a = UcumDecimal.fromString('1.50'); // precision = 3
final b = UcumDecimal.fromString('100'); // precision = 3
// From native numbers
final c = UcumDecimal.fromInt(42);
final d = UcumDecimal.fromDouble(3.14);
final e = UcumDecimal.fromNum(100);
final f = UcumDecimal.fromBigInt(BigInt.from(999));
// Named constructors
final zero = UcumDecimal.zero();
final one = UcumDecimal.one();
Arithmetic
final a = UcumDecimal.fromString('10.5');
final b = UcumDecimal.fromString('3.2');
final sum = a.add(b); // 13.7
final diff = a.subtract(b); // 7.3
final prod = a.multiply(b); // 33.60
final quot = a.divide(b); // 3.3
// Integer division and modulo
final intDiv = a.divInt(b); // 3
final mod = a.modulo(b); // 0.9
Comparison
final a = UcumDecimal.fromString('10.0');
final b = UcumDecimal.fromString('10.00');
// String equality (exact representation)
a.equals(b); // false — different precision
// Numeric value equality
a.equalsValue(b); // true — same numeric value
// Comparison (-1, 0, 1)
a.comparesTo(b); // 0
ValidatedQuantity
ValidatedQuantity combines a UcumDecimal value with a validated UCUM unit string. It supports full arithmetic operators and unit conversion.
Constructors
// Standard constructor
final q1 = ValidatedQuantity(
value: UcumDecimal.fromString('5.0'),
unit: 'mg',
);
// Parse from string ("value unit" format)
final q2 = ValidatedQuantity.fromString('5.0 mg');
// From native number
final q3 = ValidatedQuantity.fromNumber(5.0, unit: 'mg');
Arithmetic Operators
ValidatedQuantity supports +, -, *, /, ~/, and % with automatic unit handling:
final weight1 = ValidatedQuantity.fromString('70 kg');
final weight2 = ValidatedQuantity.fromString('5 kg');
final total = weight1 + weight2; // 75 kg
final diff = weight1 - weight2; // 65 kg
// Cross-unit arithmetic converts automatically
final mg = ValidatedQuantity.fromString('500 mg');
final g = ValidatedQuantity.fromString('1 g');
final sum = mg + g; // 1500 mg (converts to left operand's unit)
Comparison Operators
final a = ValidatedQuantity.fromString('1 kg');
final b = ValidatedQuantity.fromString('500 g');
print(a > b); // true (1 kg > 500 g)
print(a == b); // false
print(a >= b); // true
Unit Conversion
final temp = ValidatedQuantity.fromString('100 Cel');
final fahrenheit = temp.convertTo('[degF]');
print(fahrenheit); // 212 [degF]
Duration Support
ValidatedQuantity recognizes time units and provides convenient accessors:
final duration = ValidatedQuantity.fromString('90 min');
print(duration.isDuration); // true
print(duration.hours); // 1
print(duration.minutes); // 30
Pair
Pair is a lightweight container holding a UcumDecimal value and a unit string. It is the input/output type for UcumService.multiply(), UcumService.divideBy(), and UcumService.getCanonicalForm().
final pair = Pair(
value: UcumDecimal.fromString('100'),
unit: 'mg',
);
print(pair.value.asUcumDecimal()); // 100
print(pair.unit); // mg
Common Healthcare Unit Conversions
final ucum = UcumService();
// Weight
ucum.convert(UcumDecimal.fromString('1'), '[lb_av]', 'kg');
// 0.45359237 kg
ucum.convert(UcumDecimal.fromString('1'), 'kg', '[lb_av]');
// 2.20462262184877580996888 lb
// Volume
ucum.convert(UcumDecimal.fromString('1'), 'L', 'mL');
// 1000 mL
// Temperature
ucum.convert(UcumDecimal.fromString('37'), 'Cel', '[degF]');
// 98.6 degF
ucum.convert(UcumDecimal.fromString('98.6'), '[degF]', 'Cel');
// 37 Cel
// Length
ucum.convert(UcumDecimal.fromString('1'), '[in_i]', 'cm');
// 2.54 cm
// Time
ucum.convert(UcumDecimal.fromString('1'), 'h', 'min');
// 60 min
// Lab values
ucum.convert(UcumDecimal.fromString('1'), 'mmol/L', 'mol/L');
// 0.001 mol/L
Integration with FHIR
The ucum package is used by other FHIR-FLI packages for unit-aware calculations:
- fhir_r4_path: The FHIRPath engine uses UCUM for quantity comparisons and arithmetic in expressions like
Observation.value.where(value > 100 'mg'). - FHIR Quantity type: FHIR resources use UCUM codes in
Quantity.codefields. UseUcumService.validate()to ensure codes are valid UCUM expressions.
// Validate a FHIR Quantity's unit code
final ucum = UcumService();
final code = 'mg/dL';
final error = ucum.validate(code);
if (error != null) {
print('Invalid UCUM code: $error');
}
The package has no dependency on any FHIR packages and can be used independently in any Dart application that needs unit handling.
UCUM Specification Reference
For the complete UCUM specification, see unitsofmeasure.org. The package implements the full specification including:
- All 7 SI base units (meter, kilogram, second, ampere, kelvin, mole, candela)
- 20 SI prefixes (yocto through yotta)
- 2000+ defined units across all domains
- Special unit handlers for non-linear conversions (Celsius, Fahrenheit, pH, etc.)
- Arbitrary-precision arithmetic preserving significant figures