Unit Conversions in Programming: Libraries, APIs, and Best Practices
- Why Not Just Hardcode Conversion Factors?
- Python: pint Library (Unit-Aware Calculations)
- JavaScript: Unit Conversion Libraries
- Using Unit Conversion APIs (REST, JSON)
- Database Storage: Always Store in Base SI Units
- Display Layer: Convert for the User's Locale
- Testing Unit Conversions (Floating-Point Gotchas)
- The UnitFYI API: /api/convert/ Endpoint
Why Not Just Hardcode Conversion Factors?
A hardcoded conversion factor is fragile in at least three ways. First, it may be wrong from the start — 1 pound = 0.45359 kg (truncated) vs. 1 pound = 0.45359237 kg (exact by definition since 1959). The difference is 4.7 grams per kilogram, invisible in casual use but significant in pharmaceutical or aerospace contexts.
Second, it carries no unit context. A variable named weight_kg = user_input * 2.20462 contains an implicit assumption that user_input is in pounds. When a new developer reads this code six months later, the conversion direction is not obvious. If user_input changes to arrive in grams, the bug is silent — the factor still works numerically, just incorrectly.
Third, it does not scale. A codebase with 200 conversion factors scattered across view files, model methods, and serializers is a maintenance liability. Updating one factor when a definition changes (rare but it happens — the US survey foot was retired in 2023 in favor of the international foot) requires a grep across the entire repository.
Dedicated unit libraries and disciplined architecture solve all three problems.
Python: pint Library (Unit-Aware Calculations)
pint is the standard Python library for unit-aware quantities. It attaches physical dimension information to numeric values and enforces dimensional consistency at runtime.
from pint import UnitRegistry
ureg = UnitRegistry()
# Create unit-aware quantities
speed = 60 * ureg.miles_per_hour
distance = 100 * ureg.kilometers
# Automatic conversion
speed_si = speed.to(ureg.meters_per_second)
print(speed_si) # 26.8224 meter / second
# Dimensional mismatch raises DimensionalityError
time = distance / speed # → 1.04 hour (correct)
bad = distance + speed # → pint.errors.DimensionalityError
pint knows the exact conversion factor for every standard unit pair. 1 mph = 0.44704 m/s (exact), 1 inch = 0.0254 m (exact), 1 pound = 0.45359237 kg (exact). It also handles compound units: kg·m/s², W·h, lbf·in.
For scientific computing, pint integrates with NumPy arrays:
import numpy as np
temperatures_c = np.array([0, 20, 37, 100]) * ureg.degC
temperatures_k = temperatures_c.to(ureg.kelvin)
# → [273.15, 293.15, 310.15, 373.15] kelvin
Non-linear conversions like Celsius → Kelvin (offset + scale) are handled correctly by pint. The library also supports custom units and custom prefixes, making it suitable for domain-specific applications (e.g., medical devices, astronomy).
Install with pip install pint. The library has no compiled dependencies and runs on all Python 3.8+ platforms.
JavaScript: Unit Conversion Libraries
JavaScript has several mature unit conversion libraries for browser and Node.js contexts.
convert-units
The most popular npm package for unit conversion, with ~2M weekly downloads. It covers length, mass, volume, temperature, pressure, speed, and a dozen other categories.
import convert from 'convert-units';
// Simple conversion
convert(60).from('mph').to('m/s');
// → 26.8224
// List all units in a category
convert().possibilities('length');
// → ['mm', 'cm', 'm', 'km', 'in', 'ft', 'yd', 'mi', ...]
// Best unit for a given value (human-readable output)
convert(1500).from('m').toBest();
// → { val: 1.5, unit: 'km', plural: 'Kilometers' }
mathjs
mathjs is a comprehensive math library that includes units as a first-class feature. It supports expression parsing, which makes it useful for user-facing input fields:
import { evaluate } from 'mathjs';
evaluate('60 mph to m/s'); // 26.8224 m/s
evaluate('5 feet + 3 inches'); // 1.6002 m (returns SI)
evaluate('1 kg * 9.80665 m/s^2'); // 9.80665 N
mathjs handles unit arithmetic, not just conversion — making it the better choice when users enter expressions like "1 yard 2 feet 5 inches" rather than a single numeric value.
For browser applications without a bundler, convert-units is the lighter dependency (~30 KB minified). For applications already using mathjs for other math, using its built-in unit support avoids adding a second library.
Using Unit Conversion APIs (REST, JSON)
When the conversion logic cannot live in the client — due to platform constraints, licensing, or the need for centralized factor management — a REST API is the right architecture.
A well-designed conversion API accepts a numeric value, source unit, and target unit, and returns the converted value with metadata:
GET /api/convert/?value=100&from=km&to=mi
{
"value": 100,
"from": "km",
"to": "mi",
"result": 62.13711922,
"factor": 0.6213711922,
"category": "length"
}
The factor field is useful for client-side caching: once the km→mi factor is known, subsequent conversions can be computed locally without a round trip.
API-based unit conversion is appropriate when: - Conversion factors are updated by a central authority (e.g., exchange rates, which change continuously — though these are technically not unit conversions) - The application platform does not support a native library (e.g., shell scripts, low-power IoT firmware) - Audit logging of all conversions is required for compliance - A single source of truth is needed across multiple applications
The UnitFYI conversion API provides programmatic access to all 2,200 unit pairs across 20 categories. Browse available categories at /api/categories/.
Database Storage: Always Store in Base SI Units
The most consequential architectural decision for a unit-aware application is the storage format. The recommended pattern is: store all physical quantities in a single base SI unit for each dimension, convert only at the application boundary.
Length: store in meters. Convert to feet, inches, miles, etc. only for display.
Mass: store in kilograms. Convert to pounds, ounces, grams as needed.
Temperature: store in kelvin. Convert to Celsius or Fahrenheit for display.
Speed: store in meters per second.
Pressure: store in pascals.
Energy: store in joules.
This approach eliminates the class of bugs where a record stored in pounds is retrieved and treated as kilograms. The database column name makes the unit explicit:
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
mass_kg NUMERIC(12, 6) NOT NULL, -- always kilograms
length_m NUMERIC(12, 6), -- always meters
price_usd NUMERIC(10, 2) NOT NULL -- currency is not a physical unit
);
The column name mass_kg is not just documentation — it is a searchable convention that allows automated audits:
grep -r "mass_" models.py # find all mass fields
For Django projects, consider a custom model field that enforces the unit in the type:
class KilogramField(models.DecimalField):
"""Always stores in kilograms. Use .to(unit) for display."""
description = "Mass stored in kilograms"
Display Layer: Convert for the User's Locale
While storing in SI, render for the user's preference. The display layer is the only place unit conversion should happen.
def format_distance(meters: float, locale: str) -> str:
"""Convert meters to locale-appropriate distance string."""
if locale in ('en-US', 'en-LR', 'en-MM'):
# Only three countries use customary units for everyday distances
miles = meters / 1609.344
if miles >= 0.5:
return f"{miles:.1f} mi"
feet = meters / 0.3048
return f"{feet:.0f} ft"
elif meters >= 1000:
return f"{meters / 1000:.2f} km"
else:
return f"{meters:.0f} m"
Key principles for display conversion: - Localize, don't globalize: most of the world uses metric; serve customary units only to users who need them. - Round for readability: 1,609.344 m displayed as "1.6 km" is more useful than "1.609344 km". - Show units explicitly: never display "26.8" without specifying "m/s" or "mph". - Consider the precision: a user's height of 1.8288 m should display as "6 ft" or "6'0"", not "6.000 ft".
Testing Unit Conversions (Floating-Point Gotchas)
Floating-point arithmetic is not exact, and conversion factors that look clean in decimal often produce small rounding errors in binary floating-point:
>>> 1.1 + 2.2
3.3000000000000003
>>> 0.1 + 0.2 == 0.3
False
For unit conversions, test with exact expected values using an epsilon comparison, or use Python's decimal.Decimal for financial/exact conversions:
import pytest
def test_km_to_miles():
result = convert_km_to_miles(1.0)
assert abs(result - 0.6213711922) < 1e-9 # tolerance
def test_celsius_to_fahrenheit():
assert convert_celsius_to_fahrenheit(0) == 32.0 # exact
assert convert_celsius_to_fahrenheit(100) == 212.0 # exact
assert abs(convert_celsius_to_fahrenheit(37) - 98.6) < 1e-10
For conversions with exact definitions (inch = 0.0254 m exactly, pound = 0.45359237 kg exactly), the binary floating-point representation may not be exact, but the error is below 10⁻¹⁵ — negligible for any practical purpose. Test to the precision your application requires, not to 15 significant figures.
Property-based testing is also effective for unit conversions: if convert(convert(x, A, B), B, A) ≈ x for all x, the conversion is internally consistent:
from hypothesis import given, strategies as st
@given(st.floats(min_value=0.01, max_value=1e6))
def test_roundtrip_km_miles(value_km):
miles = convert_km_to_miles(value_km)
back = convert_miles_to_km(miles)
assert abs(back - value_km) / value_km < 1e-10
The UnitFYI API: /api/convert/ Endpoint
The UnitFYI API provides a straightforward REST interface for all conversions available on the site. Example request:
GET /api/convert/?value=60&from=mile-per-hour&to=meter-per-second
The API returns the converted value along with the unit category, enabling client applications to build their own locale-aware display logic without maintaining local conversion tables. It covers all 20 measurement categories and 220 units available on UnitFYI, including non-linear conversions (temperature scales) and compound units.
For building applications that need programmatic access to unit metadata — unit names, symbols, categories, and conversion factors — the /api/categories/ endpoint returns a complete list of supported categories. Per-category unit lists are available at /api/units/{category}/.
For interactive conversion testing during development, the unit converter tool accepts any unit pair and shows results instantly with no API key required.
RELATED ARTICLES
Why Scientists Use Kelvin Instead of Celsius
Kelvin starts at absolute zero rather than an arbitrary reference point, making it the only temperature scale where calculations in physics, chemistry, and astrophysics work correctly.
Science & TechUnits in Astronomy: From AU to Parsecs and Light-Years
Astronomers use specialized distance units — AU, light-year, and parsec — because the metric system's kilometer is simply too small to be useful beyond Earth.
Science & Tech3D Printing Units: mm, Inches, Layer Heights, and Tolerances
A complete reference for 3D printing measurement units covering STL file scaling, layer heights, nozzle sizes, filament diameters, tolerances, bed dimensions, and print speed.