Unit Converter Guide: Base Units, Floating Point, and Temperature
Unit conversion looks deceptively simple: multiply by a factor and you are done. In practice the naive approach accumulates floating-point error when you chain conversions, breaks entirely for temperature because Celsius and Fahrenheit have an offset not just a scale, and leads to silent bugs when binary and decimal interpretations of "kilobyte" are mixed. Use the Unit Converter to follow along with the examples in this guide.
This guide covers the base-unit normalization strategy that eliminates chained-conversion error, the affine formulas that temperature conversion requires, the binary-vs-decimal data size divide, the main unit categories developers encounter, precision display, and concrete developer scenarios where getting units wrong has real consequences.
Unit Categories Developers Encounter
A general-purpose unit converter works across several categories. Each has a natural base unit that everything else reduces to:
| Category | SI base unit | Common conversions |
|---|---|---|
| Length | metre (m) | km, cm, mm, inch, foot, yard, mile |
| Mass / weight | kilogram (kg) | gram, tonne, pound, ounce, stone |
| Temperature | kelvin (K) | Celsius, Fahrenheit — offset required |
| Volume | litre (L) or cubic metre | mL, fluid oz, cup, gallon, pint |
| Speed | metres per second (m/s) | km/h, mph, knots, ft/s |
| Data size | bit or byte | KB/KiB, MB/MiB, GB/GiB (see below) |
| Time | second (s) | ms, minutes, hours, days, weeks |
Keeping this table in mind matters when you implement conversions in code. Reducing every value to the base unit first, and then scaling to the target, is the key to avoiding accumulated rounding error — covered in detail in the next section.
Base-Unit Normalization: Avoiding Floating-Point Chains
The naive approach to building a unit converter is to write a direct formula for every pair of units: inches to centimetres, centimetres to millimetres, millimetres to feet, and so on. With n units you need on the order of n² formulas, and each chained multiplication introduces a fresh rounding error.
The correct approach is to store one canonical factor per unit — how many base units one of that unit equals — and always convert via the base unit in two steps:
- Input → base unit: multiply the value by the input unit's factor.
- Base unit → output: divide by the output unit's factor.
// All length factors expressed in metres
const LENGTH = {
metre: 1,
kilometre: 1000,
centimetre: 0.01,
millimetre: 0.001,
inch: 0.0254,
foot: 0.3048,
yard: 0.9144,
mile: 1609.344,
nauticalMile: 1852,
};
function convertLength(value, from, to) {
const metres = value * LENGTH[from]; // step 1: to base unit
return metres / LENGTH[to]; // step 2: to target unit
}
// inches to feet — two multiplications, minimal error
console.log(convertLength(72, "inch", "foot")); // 6
console.log(convertLength(1, "mile", "kilometre")); // 1.609344Compare that with the naive chain: inches → centimetres → millimetres → feet. Each intermediate step multiplies floating-point error. With the base-unit strategy you always do exactly two floating-point operations regardless of how exotic the path is.
Why floating-point error accumulates in chains
IEEE 754 double-precision floats represent most decimal fractions as repeating binary fractions. The number 0.1 in JavaScript is actually 0.1000000000000000055511151231257827021181583404541015625. Multiplying several such numbers together compounds the error. The base-unit approach keeps the chain at exactly two operations, so the maximum rounding error is bounded and predictable.
// Naive chained: inch -> cm -> mm -> feet
const inchToCm = 2.54;
const cmToMm = 10;
const mmToFoot = 0.00328084;
const naive = 72 * inchToCm * cmToMm * mmToFoot;
console.log(naive); // 5.999999... instead of 6
// Base-unit strategy: always exact within float precision
console.log(convertLength(72, "inch", "foot")); // 6Temperature: Affine Formulas, Not Just Scale Factors
Temperature conversion is the most common place unit-converter implementations go wrong. Celsius, Fahrenheit, and Kelvin do not share a common zero point, so you cannot convert between them with a single multiplication. You need an affine transformation: a scale and an offset.
| From → To | Formula | Example |
|---|---|---|
| Celsius → Fahrenheit | F = C × 9/5 + 32 | 100 °C = 212 °F |
| Fahrenheit → Celsius | C = (F − 32) × 5/9 | 32 °F = 0 °C |
| Celsius → Kelvin | K = C + 273.15 | 0 °C = 273.15 K |
| Kelvin → Celsius | C = K − 273.15 | 373.15 K = 100 °C |
| Fahrenheit → Kelvin | K = (F − 32) × 5/9 + 273.15 | 212 °F = 373.15 K |
Notice that every formula has both a multiplier and an additive offset. If you omit the offset and just multiply by a scale factor, your conversion will be wrong for every value except absolute zero. This is the single most frequent bug in hand-rolled temperature conversion code.
// WRONG: temperature is not just a scale
function badCelsiusToFahrenheit(c) {
return c * (9 / 5); // missing + 32
}
console.log(badCelsiusToFahrenheit(100)); // 180 — should be 212
// CORRECT: affine transformation
function celsiusToFahrenheit(c) {
return c * (9 / 5) + 32;
}
function fahrenheitToCelsius(f) {
return (f - 32) * (5 / 9);
}
function celsiusToKelvin(c) {
return c + 273.15;
}
function kelvinToCelsius(k) {
return k - 273.15;
}
// Universal temperature converter via Kelvin as base unit
function toKelvin(value, unit) {
if (unit === "celsius") return value + 273.15;
if (unit === "fahrenheit") return (value - 32) * (5 / 9) + 273.15;
if (unit === "kelvin") return value;
throw new Error(`Unknown temperature unit: ${unit}`);
}
function fromKelvin(kelvin, unit) {
if (unit === "celsius") return kelvin - 273.15;
if (unit === "fahrenheit") return (kelvin - 273.15) * (9 / 5) + 32;
if (unit === "kelvin") return kelvin;
throw new Error(`Unknown temperature unit: ${unit}`);
}
function convertTemperature(value, from, to) {
return fromKelvin(toKelvin(value, from), to);
}
console.log(convertTemperature(100, "celsius", "fahrenheit")); // 212
console.log(convertTemperature(32, "fahrenheit", "celsius")); // 0
console.log(convertTemperature(0, "celsius", "kelvin")); // 273.15The pattern mirrors the base-unit strategy: convert to Kelvin first, then convert from Kelvin to the target. Each direction requires its own offset arithmetic — you cannot factor out a single multiplier table for temperatures.
Data Size: Binary vs Decimal — Where Each Is Correct
Data size units are the source of persistent confusion because two competing standards use the same prefixes with different meanings. The difference reaches 7.4% at the gigabyte level and grows with each prefix step.
| Unit | System | Bytes | Where used |
|---|---|---|---|
| KB (kilobyte) | Decimal (SI) | 1,000 | Storage marketing, network bandwidth, HTTP headers |
| KiB (kibibyte) | Binary (IEC) | 1,024 | RAM, OS file sizes, kernel buffers |
| MB (megabyte) | Decimal | 1,000,000 | Hard disk capacity, transfer rates |
| MiB (mebibyte) | Binary | 1,048,576 | Process memory, JVM heap, container limits |
| GB (gigabyte) | Decimal | 1,000,000,000 | Disk specs, cloud storage pricing |
| GiB (gibibyte) | Binary | 1,073,741,824 | Kubernetes memory limits, OS reports |
Storage manufacturers use decimal prefixes (1 GB = 1,000,000,000 bytes) because the numbers look larger. Operating systems historically used binary math and reported the same disk as smaller. When you set a Kubernetes memory limit of 1Gi you mean 1,073,741,824 bytes. When an S3 bucket shows you "1.2 GB transferred" it means 1,200,000,000 bytes. Mixing them silently is a real operational hazard.
For data-size conversions see also the Data Size Converter, which handles both binary (IEC) and decimal (SI) prefixes explicitly.
// Data size conversion — always be explicit about the base
const BYTES = {
// Decimal (SI)
KB: 1_000,
MB: 1_000_000,
GB: 1_000_000_000,
TB: 1_000_000_000_000,
// Binary (IEC)
KiB: 1_024,
MiB: 1_048_576,
GiB: 1_073_741_824,
TiB: 1_099_511_627_776,
};
function convertDataSize(value, from, to) {
const bytes = value * BYTES[from];
return bytes / BYTES[to];
}
// A 500 GB drive advertised in decimal
const driveBytes = 500 * BYTES.GB;
console.log(driveBytes / BYTES.GiB); // ~465.7 GiB — what the OS reportsDeveloper Use Cases: Config Thresholds, API Limits, Timeouts
Unit conversion is not just a calculator curiosity — it appears in real production code every day:
Config file thresholds
Infrastructure config files often use inconsistent units. One tool expects milliseconds, another seconds, a third accepts human strings like "30s". Keeping a canonical converter function prevents copy-paste errors when migrating values between tools.
const TIME_MS = {
ms: 1,
second: 1_000,
minute: 60_000,
hour: 3_600_000,
day: 86_400_000,
};
function toMs(value, unit) {
return value * TIME_MS[unit];
}
// nginx uses seconds, Redis uses milliseconds, cron uses minutes
const SESSION_TIMEOUT_MS = toMs(30, "minute"); // 1_800_000 ms
const NGINX_TIMEOUT_S = SESSION_TIMEOUT_MS / TIME_MS.second; // 1800
const REDIS_TTL_MS = SESSION_TIMEOUT_MS; // already in msAPI rate and size limits
Cloud APIs express limits in different units. AWS Lambda has a 6 MB payload limit (decimal megabytes). Stripe enforces request body sizes in bytes. PostgreSQL's work_mem setting accepts values like '64MB' where MB historically meant MiB in that context. Always read the documentation to confirm which convention applies.
# Python: checking a payload against AWS Lambda's 6 MB limit
LAMBDA_MAX_BYTES = 6 * 1_000_000 # decimal MB, as documented by AWS
def check_payload(data: bytes) -> bool:
if len(data) > LAMBDA_MAX_BYTES:
mb = len(data) / 1_000_000
raise ValueError(f"Payload {mb:.2f} MB exceeds Lambda 6 MB limit")
return TrueKubernetes and container memory
Kubernetes uses binary suffixes (Ki, Mi, Gi) in resource manifests. Setting the wrong suffix can over-provision or cause OOM kills.
# 512 MiB = 536,870,912 bytes
kubectl set resources deployment/api --limits=memory=512Mi
# 512 MB = 512,000,000 bytes — about 5% less
kubectl set resources deployment/api --limits=memory=512MGeographic coordinate precision
GPS coordinates are angles measured in degrees. Developers sometimes need to convert between decimal degrees and degrees-minutes-seconds (DMS). One degree of latitude is approximately 111 km; one arcsecond is about 30 metres. Storing coordinates with fewer than five decimal places introduces meaningful positional error in location-sensitive applications.
function decimalToDMS(decimal) {
const deg = Math.trunc(decimal);
const minFloat = Math.abs(decimal - deg) * 60;
const min = Math.trunc(minFloat);
const sec = (minFloat - min) * 60;
return { degrees: deg, minutes: min, seconds: sec };
}
console.log(decimalToDMS(48.8566));
// { degrees: 48, minutes: 51, seconds: 23.76 }
// Paris latitude — 48° 51' 23.76"Precision and Significant Figures
After converting, you usually want to display a rounded result rather than the full floating-point expansion. Two common approaches:
- Fixed decimal places —
value.toFixed(4)always shows four digits after the decimal point. Good for sensor readings or currency where the scale is known. - Significant figures —
value.toPrecision(4)keeps four significant digits regardless of magnitude. Better for scientific values that span many orders of magnitude.
const miles = convertLength(42.195, "kilometre", "mile"); // marathon distance
console.log(miles.toFixed(3)); // "26.219"
console.log(miles.toPrecision(5)); // "26.219"
// Very small values — toPrecision is cleaner
const nm = convertLength(1, "inch", "nanometre"); // 25,400,000
console.log(nm.toFixed(0)); // "25400000"
console.log(nm.toPrecision(4)); // "2.540e+7"
// Avoid displaying raw float noise
const noisy = 0.1 + 0.2; // 0.30000000000000004
console.log(+noisy.toPrecision(10)); // 0.3For user-facing output, a common pattern is to pick a fixed number of decimal places based on the magnitude: more places for small values, fewer for large ones. The Unit Converter handles this automatically.
Speed and Volume
Speed
Speed is a derived unit (distance divided by time), so the base-unit strategy applies with a combined factor. Store each speed unit as its equivalent in metres per second:
const SPEED_MPS = {
mps: 1, // metres per second
kph: 1 / 3.6, // kilometres per hour
mph: 0.44704, // miles per hour
knot: 0.514444, // nautical miles per hour
fps: 0.3048, // feet per second
};
function convertSpeed(value, from, to) {
return value * SPEED_MPS[from] / SPEED_MPS[to];
}
console.log(convertSpeed(100, "kph", "mph").toFixed(2)); // "62.14"
console.log(convertSpeed(1, "knot", "kph").toFixed(4)); // "1.8520"Volume
Volume conversions bridge the metric and US customary systems, where "fluid ounce", "cup", and "pint" all have different sizes in UK vs US conventions. Always confirm which regional standard applies before converting cooking or pharmaceutical measurements.
const VOLUME_LITRES = {
litre: 1,
millilitre: 0.001,
cubicMetre: 1000,
// US customary
usFluidOz: 0.0295735,
usCup: 0.236588,
usPint: 0.473176,
usGallon: 3.78541,
// UK / Imperial
ukFluidOz: 0.0284131,
ukPint: 0.568261,
ukGallon: 4.54609,
};
function convertVolume(value, from, to) {
return value * VOLUME_LITRES[from] / VOLUME_LITRES[to];
}
// 1 US gallon in litres
console.log(convertVolume(1, "usGallon", "litre").toFixed(5)); // "3.78541"Related Reading
If data-size conversions are your main concern, the Data Size Converter Guide goes deeper on binary vs decimal prefixes, IEC standard naming, and common pitfalls when comparing disk manufacturer specs with OS-reported sizes.
Convert between length, mass, temperature, speed, volume, time, and data sizes instantly with the Unit Converter — runs entirely in your browser so nothing leaves your machine. For binary vs decimal data-size precision, use the dedicated Data Size Converter.