Unit Converter: From Floating-Point Precision to Temperature Offsets
Unit Converter: From Floating-Point Precision to Temperature Offsets#
I recently needed to convert between inches and centimeters for a project. Wrote a quick formula, but the result didn’t match the design specs—that’s when I realized unit conversion isn’t as simple as it looks.
Basic Conversion: The Base Unit Method#
The obvious approach is direct formulas:
// Inch to centimeter
const inchToCm = (inch) => inch * 2.54
// Centimeter to inch
const cmToInch = (cm) => cm / 2.54
Problem: with 10 length units, you’d need 90 conversion functions. Not practical.
A better approach is the base unit method: pick one unit as the base (e.g., meter), and store only the conversion factor relative to that base.
interface Unit {
name: string
symbol: string
factor: number // Multiplier relative to base unit
}
const lengthUnits = {
m: { name: 'Meter', symbol: 'm', factor: 1 }, // Base unit
km: { name: 'Kilometer', symbol: 'km', factor: 1000 },
cm: { name: 'Centimeter', symbol: 'cm', factor: 0.01 },
mm: { name: 'Millimeter', symbol: 'mm', factor: 0.001 },
in: { name: 'Inch', symbol: 'in', factor: 0.0254 },
ft: { name: 'Foot', symbol: 'ft', factor: 0.3048 },
mi: { name: 'Mile', symbol: 'mi', factor: 1609.344 },
}
Convert by going through the base unit:
function convert(value: number, from: Unit, to: Unit): number {
const baseValue = value * from.factor // Convert to base unit (meters)
return baseValue / to.factor // Convert to target unit
}
// 10 miles = ? kilometers
convert(10, lengthUnits.mi, lengthUnits.km) // 16.09344
Now N units only need N factor definitions—much easier to maintain.
The Temperature Exception#
Temperature can’t use simple factor conversion because their zero points differ. 0°C is water’s freezing point, 0°F is a brine mixture temperature, and 0K is absolute zero.
Temperature conversion needs an offset:
const tempUnits = {
c: { name: 'Celsius', symbol: '°C', factor: 1, offset: 0 },
f: { name: 'Fahrenheit', symbol: '°F', factor: 5/9, offset: -32 },
k: { name: 'Kelvin', symbol: 'K', factor: 1, offset: -273.15 },
}
The conversion logic changes:
function convertTemperature(value: number, from: Unit, to: Unit): number {
// First convert to Celsius (as base)
let celsius = (value + from.offset) * from.factor
// Then convert to target unit
return celsius / to.factor - to.offset
}
// 100°C = ? °F
convertTemperature(100, tempUnits.c, tempUnits.f) // 212
A detail here: factor: 5/9, not factor: 9/5. Converting Fahrenheit to Celsius uses (F - 32) × 5/9, so factor is 5/9.
Floating-Point Precision Issues#
Unit converters often hit precision problems:
0.1 + 0.2 // 0.30000000000000004
0.1 * 3 // 0.30000000000000004
This causes weird results in conversions:
// 1/3 foot to inches
const feet = 1/3
const inches = feet * 12 // 3.9999999999999996
Solutions:
1. Reasonable Decimal Places#
function formatResult(value: number): string {
// Keep at most 6 decimals, strip trailing zeros
return value.toFixed(6).replace(/\.?0+$/, '')
}
formatResult(3.9999999999999996) // "4"
2. Use a Precision Library#
For exact calculations (finance, science), use decimal.js:
import Decimal from 'decimal.js'
function convertPrecise(value: number, from: Unit, to: Unit): string {
const baseValue = new Decimal(value).times(from.factor)
return baseValue.div(to.factor).toDecimalPlaces(6).toString()
}
3. Choose Appropriate Factor Precision#
Factor precision affects final results. A mile is exactly 1609.344 meters, not 1609:
// Imprecise
mi: { factor: 1609 }
// Precise
mi: { factor: 1609.344 }
Errors compound: 100 miles × 0.344 m/mi = 34.4 meters of error.
Binary Prefix vs Decimal Prefix#
Data storage has a gotcha: is KB 1000 bytes or 1024 bytes?
// Option 1: Decimal prefix (hard drive manufacturers)
const dataUnits = {
KB: { factor: 1000 },
MB: { factor: 1000000 },
GB: { factor: 1000000000 },
}
// Option 2: Binary prefix (operating systems)
const dataUnits = {
KB: { factor: 1024 },
MB: { factor: 1048576 },
GB: { factor: 1073741824 },
}
That’s why your 500GB hard drive shows only 465GB on your computer: manufacturers use 1000, systems use 1024.
500 * 1000 * 1000 * 1000 / (1024 * 1024 * 1024) // 465.66 GB
In a unit converter, you need to specify which standard you use. I chose binary (1024) since developers are more familiar with it.
Performance: Avoid Recalculation#
When users frequently switch units, cache intermediate results:
function useConverter(category: string, value: number) {
const [baseValue, setBaseValue] = useState(0)
// Update base value when input changes
useEffect(() => {
const from = getCurrentUnit(category)
setBaseValue(value * from.factor)
}, [value, category])
// Use cached base value when switching units
const convertTo = useCallback((to: Unit) => {
return baseValue / to.factor
}, [baseValue])
return { convertTo }
}
The Result#
Based on these ideas, I built: Unit Converter
Features:
- 6 categories: length, weight, area, volume, temperature, data storage
- Accurate floating-point calculations
- Real-time bidirectional conversion
The core code is only about 200 lines, but handling precision, temperature offsets, and binary prefixes properly makes it reliable.
Related tools: PX REM Converter | Timestamp Converter