Format Bytes as a Human String
Showing `1457280 bytes` is hostile UX; showing `1.39 MB` (or `1.46 MB` if you use SI) is what users expect. This snippet covers the binary IEC variant (KiB, MiB, GiB), the decimal SI variant (KB, MB, GB), and a configurable helper that picks the unit, precision, and separator. Use it for upload progress, storage dashboards, or anywhere a raw byte count would otherwise leak into the UI.
399 views
6
function formatBytesBinary(bytes, decimals = 2) {
if (bytes === 0) return '0 B';
const units = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB'];
const i = Math.floor(Math.log(Math.abs(bytes)) / Math.log(1024));
const idx = Math.min(i, units.length - 1);
const value = bytes / Math.pow(1024, idx);
return `${value.toFixed(decimals)} ${units[idx]}`;
}
console.log(formatBytesBinary(0)); // 0 B
console.log(formatBytesBinary(1024)); // 1.00 KiB
console.log(formatBytesBinary(1048576)); // 1.00 MiB
console.log(formatBytesBinary(1500000)); // 1.43 MiBPicking the right unit reduces to dividing by 1024 until the value is small, which Math.log(bytes) / Math.log(1024) does in a single step. Flooring the log gives the unit index; clamping with Math.min prevents overflow past the largest unit you have a label for. IEC units (KiB, MiB) are technically correct for binary multiples and are increasingly common in developer tools (Linux du -h, GitHub release sizes). Use Math.abs(bytes) so negative values (rare, but seen in delta accounting) format the same way.
function formatBytesSI(bytes, decimals = 2) {
if (bytes === 0) return '0 B';
const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
const i = Math.floor(Math.log(Math.abs(bytes)) / Math.log(1000));
const idx = Math.min(i, units.length - 1);
const value = bytes / Math.pow(1000, idx);
return `${value.toFixed(decimals)} ${units[idx]}`;
}
console.log(formatBytesSI(1000)); // 1.00 KB
console.log(formatBytesSI(1500000)); // 1.50 MB
console.log(formatBytesSI(1024)); // 1.02 KBDisk vendors, cloud providers, and most consumer-facing UIs use the decimal (SI) convention: 1 KB = 1000 B, 1 MB = 1000 KB. The math is identical to the binary version with 1024 swapped for 1000. Pick this when you want display values that match what the user reads on a hardware label or storage console; pick the binary version when the audience is technical and the source is in-memory bytes (Node.js process.memoryUsage, JVM heap stats). Mixing the two in the same UI is the surest way to create distrust.
function formatBytes(bytes, { binary = true, decimals = 2, separator = ' ' } = {}) {
if (bytes === 0) return `0${separator}B`;
const base = binary ? 1024 : 1000;
const units = binary
? ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB']
: ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
const i = Math.floor(Math.log(Math.abs(bytes)) / Math.log(base));
const idx = Math.min(i, units.length - 1);
const value = bytes / Math.pow(base, idx);
const trimmed = idx === 0 ? value.toString() : value.toFixed(decimals);
return `${trimmed}${separator}${units[idx]}`;
}
console.log(formatBytes(2048)); // 2.00 KiB
console.log(formatBytes(2048, { binary: false })); // 2.05 KB
console.log(formatBytes(2048, { decimals: 1, separator: '\u00A0' }));A single helper with options handles the most common variations: pick decimal vs binary, set the precision, and choose a separator (a regular space, a non-breaking space \u00A0 so the number and unit do not wrap, or even an empty string for tight CSS). Showing whole bytes without trailing zeros (512 B instead of 512.00 B) is a small touch that makes the output feel less robotic; the idx === 0 branch handles that. Wrap this in your design system's number formatter (Intl.NumberFormat for locale-aware decimals) when localisation matters.
