Unit conversion maps a quantity from one unit to another without changing the physical quantity. Reliable software must preserve the dimension, use an authoritative factor or affine formula, distinguish regional conventions, and apply an explicit precision and rounding policy. A numerically plausible result can still be wrong when the source unit, dimension, or convention is wrong.
The 2019 SI Redefinition
On May 20, 2019, the International System of Units was redefined. All seven base units are now defined by fixing the numerical values of fundamental physical constants:
| Base unit | Defined by | Fixed constant value |
|---|---|---|
| Second (s) | Cesium-133 hyperfine transition | Δν_Cs = 9,192,631,770 Hz |
| Meter (m) | Speed of light | c = 299,792,458 m/s |
| Kilogram (kg) | Planck constant | h = 6.62607015 × 10⁻³⁴ J·s |
| Ampere (A) | Elementary charge | e = 1.602176634 × 10⁻¹⁹ C |
| Kelvin (K) | Boltzmann constant | k = 1.380649 × 10⁻²³ J/K |
| Mole (mol) | Avogadro constant | N_A = 6.02214076 × 10²³ mol⁻¹ |
| Candela (cd) | Luminous efficacy | K_cd = 683 lm/W |
The key change: the kilogram is no longer defined by a physical artifact (the International Prototype Kilogram in Paris). It's now defined by the Planck constant. This means any laboratory with the right equipment can independently realize the kilogram to full precision.
Why This Matters for Software
Many relationships among SI units and some legally defined non-SI conversions are exact, but the 2019 redefinition did not make every conversion factor exact. Measured, conventional, environmental, and context-dependent quantities can remain approximate. Examples of exact definitions include:
- 1 inch = exactly 25.4 mm (by definition since 1959)
- 1 pound = exactly 0.45359237 kg (by definition)
- °C = K − 273.15 (exact offset)
Even when a factor is exact, software can introduce representation and rounding error. When a factor itself is measured or conventional, both coefficient uncertainty and numeric representation matter.
Floating-Point Precision in Conversion Chains
The Problem
# Seemingly correct round-trip conversion
meters = 1.0
feet = meters / 0.3048 # 3.280839895013123
back_to_meters = feet * 0.3048 # 0.9999999999999999 (not 1.0!)
This isn't a conversion factor error — it's IEEE 754 binary floating-point representation. The number 0.3048 cannot be represented exactly in binary.
Chain Conversion Accumulates Error
# Converting through intermediate units accumulates error
km = 1.0
miles = km / 1.60934
yards = miles * 1760
feet = yards * 3
inches = feet * 12
cm = inches * 2.54
back_to_km = cm / 100000
# Result: 0.9999999406318768 (error: ~6 × 10⁻⁸)
Mitigation Strategies
1. Convert through base unit only (hub-and-spoke):
def convert(value: float, from_unit: str, to_unit: str) -> float:
# Always convert from_unit → base → to_unit (one multiplication, one division)
to_base = {'km': 1000, 'm': 1, 'cm': 0.01, 'ft': 0.3048, 'in': 0.0254}
base_value = value * to_base[from_unit]
return base_value / to_base[to_unit]
2. Use exact rational arithmetic for critical applications:
from fractions import Fraction
# Exact: 1 inch = 254/10000 meters = 127/5000 meters
INCH_TO_METER = Fraction(254, 10000)
FOOT_TO_METER = INCH_TO_METER * 12 # Fraction(3048, 10000)
# Round-trip is exact
meters = Fraction(1)
feet = meters / FOOT_TO_METER
back = feet * FOOT_TO_METER # Fraction(1, 1) — exactly 1
3. Use Decimal for currency-adjacent conversions:
from decimal import Decimal
# Weight pricing: $4.99/lb, customer orders 2.5 kg
price_per_lb = Decimal('4.99')
weight_kg = Decimal('2.5')
kg_to_lb = Decimal('2.20462')
total = price_per_lb * weight_kg * kg_to_lb # Decimal('27.527505')
Dimensional Analysis: Catching Unit Bugs at Design Time
Dimensional analysis is the principle that physical equations must be dimensionally consistent. Using it as a design technique catches unit bugs before they compile.
The Mars Climate Orbiter
In 1999, NASA lost the Mars Climate Orbiter after a navigation interface mismatch contributed to an unintended atmospheric trajectory:
- Lockheed Martin's ground software output impulse in pound-force seconds (lbf·s)
- NASA's navigation system expected newton seconds (N·s)
- The factor of 4.45 difference caused the spacecraft to enter Mars' atmosphere at the wrong altitude
The lesson is broader than one function signature: interface requirements, verification, end-to-end testing, and unit-aware data contracts all failed to prevent the mismatch. Typed quantities can remove one important class of error, but they do not replace systems engineering.
Dimensional Analysis as Code
The key insight: if units are tracked in the type system, unit mismatches become compile errors, not runtime bugs.
velocity = distance / time ✓ [m/s] = [m] / [s]
force = mass × acceleration ✓ [N] = [kg] × [m/s²]
energy = force × distance ✓ [J] = [N] × [m]
If you accidentally add meters to seconds, a dimensionally-typed system rejects it at compile time.
Type-Safe Units in Programming
F# Units of Measure (Compile-Time, Zero Runtime Cost)
[<Measure>] type m // meter
[<Measure>] type s // second
[<Measure>] type kg // kilogram
let distance = 100.0<m>
let time = 9.58<s>
let speed = distance / time // inferred type: float<m/s>
// Compile error: cannot add meters and seconds
// let nonsense = distance + time
let mass = 70.0<kg>
let acceleration = 9.81<m/s^2>
let force = mass * acceleration // float<kg m/s^2> = Newton
F# erases units at compile time — zero runtime overhead, full type safety.
Rust: Phantom Types (uom crate)
use uom::si::f64::*;
use uom::si::length::{meter, foot};
use uom::si::time::second;
let distance = Length::new::<meter>(100.0);
let time = Time::new::<second>(9.58);
let speed = distance / time; // Velocity type
// Convert to different units
let feet = distance.get::<foot>(); // 328.084...
// Compile error: cannot add Length and Time
// let nonsense = distance + time;
Python: Pint (Runtime Checking)
import pint
ureg = pint.UnitRegistry()
distance = 100 * ureg.meter
time = 9.58 * ureg.second
speed = distance / time # 10.44 meter / second
# Automatic conversion
speed_mph = speed.to(ureg.mile / ureg.hour)
print(speed_mph) # 23.35 mile / hour
# Runtime error: cannot add incompatible dimensions
# distance + time → DimensionalityError
TypeScript: Branded Types (Lightweight Approach)
type Meters = number & { readonly __brand: 'meters' };
type Feet = number & { readonly __brand: 'feet' };
type Seconds = number & { readonly __brand: 'seconds' };
function metersToFeet(m: Meters): Feet {
return (m * 3.28084) as Feet;
}
function speed(distance: Meters, time: Seconds): number {
return distance / time;
}
const d = 100 as Meters;
const t = 10 as Seconds;
const v = speed(d, t);
// Type error with strict checking:
// speed(d as Meters, d as Meters) ← won't catch this with branded types alone
Branded types provide partial safety — they prevent passing feet where meters are expected, but don't validate dimensional equations.
Binary vs Decimal Prefixes
The Confusion
| Prefix | SI (decimal) | Binary (IEC/IEEE 1541) |
|---|---|---|
| kilo/kibi | 1 KB = 1,000 bytes | 1 KiB = 1,024 bytes |
| mega/mebi | 1 MB = 1,000,000 bytes | 1 MiB = 1,048,576 bytes |
| giga/gibi | 1 GB = 1,000,000,000 bytes | 1 GiB = 1,073,741,824 bytes |
| tera/tebi | 1 TB = 10¹² bytes | 1 TiB = 2⁴⁰ bytes |
Who Uses Which
| Context | Convention | Example |
|---|---|---|
| Hard drive manufacturers | Decimal (SI) | "1 TB" = 1,000,000,000,000 bytes |
| RAM specifications | Binary | "16 GB" actually = 16 GiB = 17,179,869,184 bytes |
| macOS (since 10.6) | Decimal | Displays "500 GB" as 500,000,000,000 bytes |
| Windows | Binary | Displays "476 GB" for the same drive |
| Linux (most tools) | Binary | df -h shows GiB but labels it "G" |
| Network speeds | Decimal | "1 Gbps" = 1,000,000,000 bits/second |
The Software Problem
# "How much free space do I have?"
# Same drive, different answers:
disk_bytes = 500_107_862_016
# macOS / drive manufacturer (decimal)
disk_gb_decimal = disk_bytes / 1_000_000_000 # 500.1 GB
# Windows / most Linux tools (binary)
disk_gib = disk_bytes / (1024 ** 3) # 465.8 GiB (displayed as "465.8 GB")
# The "missing" 34 GB is purely a labeling difference
Correct Implementation
def format_bytes(size: int, binary: bool = True) -> str:
if binary:
units = ['B', 'KiB', 'MiB', 'GiB', 'TiB']
base = 1024
else:
units = ['B', 'KB', 'MB', 'GB', 'TB']
base = 1000
for unit in units:
if abs(size) < base:
return f"{size:.1f} {unit}"
size /= base
return f"{size:.1f} PiB" if binary else f"{size:.1f} PB"
Reference Tables
Length
| From | To | Factor (exact where noted) |
|---|---|---|
| 1 inch | centimeter | 2.54 (exact by definition) |
| 1 foot | meter | 0.3048 (exact) |
| 1 yard | meter | 0.9144 (exact) |
| 1 mile | kilometer | 1.609344 (exact) |
| 1 nautical mile | meter | 1852 (exact) |
Mass
| From | To | Factor |
|---|---|---|
| 1 pound (avoirdupois) | kilogram | 0.45359237 (exact) |
| 1 ounce | gram | 28.349523125 (exact) |
| 1 troy ounce | gram | 31.1034768 (exact) |
| 1 stone | kilogram | 6.35029318 |
| 1 short ton (US) | kilogram | 907.18474 (exact) |
Temperature
| Conversion | Formula | Note |
|---|---|---|
| °C → °F | °F = °C × 9/5 + 32 | Exact (rational coefficients) |
| °F → °C | °C = (°F − 32) × 5/9 | Exact |
| °C → K | K = °C + 273.15 | Exact (by definition) |
| °F → °R | °R = °F + 459.67 | Rankine scale |
Cross-reference: −40° is the same in both Celsius and Fahrenheit.
Speed
| From | To | Factor |
|---|---|---|
| 1 km/h | m/s | 1/3.6 (exact) |
| 1 mph | km/h | 1.609344 (exact) |
| 1 knot | km/h | 1.852 (exact) |
| Mach 1 | m/s | ~343 (varies with temperature and altitude) |
| 1 c (light speed) | m/s | 299,792,458 (exact, defines the meter) |
Common Conversion Pitfalls
1. US vs Imperial Gallons
US gallon = 3.785411784 liters
Imperial gallon = 4.54609 liters
Difference: 20%! A "gallon" in UK recipes is 20% larger than US.
2. Troy vs Avoirdupois Ounces
Troy ounce (gold, silver) = 31.1 g
Avoirdupois ounce (everything else) = 28.35 g
1 troy pound = 12 troy ounces = 373.2 g
1 avoirdupois pound = 16 ounces = 453.6 g
3. Calorie vs kilocalorie
1 calorie (cal) = 4.184 joules (energy to heat 1g water by 1°C)
1 Calorie (Cal) = 1 kilocalorie = 4184 joules
Food labels use "Calories" (capital C) = kilocalories
4. Short Ton vs Long Ton vs Metric Ton
Short ton (US) = 2000 lb = 907.185 kg
Long ton (UK) = 2240 lb = 1016.047 kg
Metric ton (tonne) = 1000 kg = 2204.6 lb
Use the Converter
For a quick browser calculation, the Unit Converter covers length, mass, temperature, area, volume, speed, time, binary storage, pressure, energy, power, and force. It displays rounded results and identifies US liquid and IEC binary units; use the unit conversion glossary for the concise definition. Safety-critical, regulated, trade, dosing, and laboratory work still require the applicable standard and a validated calculation path.
Primary Sources
- BIPM SI Brochure, 9th edition — SI definitions, constants, prefixes, and unit-writing rules
- NIST Guide for the Use of the International System of Units — US guidance on SI use and conversion presentation
- NASA Mars Climate Orbiter Mishap Investigation Board report — interface-unit mismatch findings
- IEEE 754-2019 — floating-point arithmetic standard
Summary
Unit conversion in software is not just table lookup — it's a type safety problem:
- Some conversion factors are exact, while measured or context-dependent values may not be; floating-point representation can add error in either case
- Hub-and-spoke conversion (always through base unit) minimizes accumulated error
- Dimensional analysis catches unit bugs at design time — the Mars Climate Orbiter proved the cost of ignoring it
- Type-safe unit systems (F# units of measure, Rust uom, Python Pint) make unit mismatches impossible or detectable
- Binary vs decimal prefixes remain a persistent source of user confusion — always specify which convention you're using
- Same-name units differ by region (US vs Imperial gallon, troy vs avoirdupois ounce) — context determines interpretation