Reputation: 46477
I'm using this function to convert a file size in bytes to a human-readable file size:
function getReadableFileSizeString(fileSizeInBytes) {
var i = -1;
var byteUnits = [' kB', ' MB', ' GB', ' TB', 'PB', 'EB', 'ZB', 'YB'];
do {
fileSizeInBytes /= 1024;
i++;
} while (fileSizeInBytes > 1024);
return Math.max(fileSizeInBytes, 0.1).toFixed(1) + byteUnits[i];
}
console.log(getReadableFileSizeString(1551859712)); // output is "1.4 GB"
However, it seems like this isn't 100% accurate. For example:
getReadableFileSizeString(1551859712); // output is "1.4 GB"
Shouldn't this be "1.5 GB"
? It seems like the division by 1024 is losing precision. Am I totally misunderstanding something or is there a better way to do this?
Upvotes: 379
Views: 217479
Reputation: 16706
Here is a prototype to convert a number to a readable string respecting the new international standards.
There are two ways to represent big numbers: You could either display them in multiples of 1000 = 10 3 (base 10) or 1024 = 2 10 (base 2). If you divide by 1000, you probably use the SI prefix names, if you divide by 1024, you probably use the IEC prefix names. The problem starts with dividing by 1024. Many applications use the SI prefix names for it and some use the IEC prefix names. The current situation is a mess. If you see SI prefix names you do not know whether the number is divided by 1000 or 1024
https://wiki.ubuntu.com/UnitsPolicy
http://en.wikipedia.org/wiki/Template:Quantities_of_bytes
Object.defineProperty(Number.prototype,'fileSize',{value:function(a,b,c,d){
return (a=a?[1e3,'k','B']:[1024,'K','iB'],b=Math,c=b.log,
d=c(this)/c(a[0])|0,this/b.pow(a[0],d)).toFixed(2)
+' '+(d?(a[1]+'MGTPEZY')[--d]+a[2]:'Bytes');
},writable:false,enumerable:false});
This function contains no loop
, and so it's probably faster than some other functions.
Usage:
IEC prefix
console.log((186457865).fileSize()); // default IEC (power 1024)
//177.82 MiB
//KiB,MiB,GiB,TiB,PiB,EiB,ZiB,YiB
SI prefix
console.log((186457865).fileSize(1)); //1,true for SI (power 1000)
//186.46 MB
//kB,MB,GB,TB,PB,EB,ZB,YB
i set the IEC as default because i always used binary mode to calculate the size of a file... using the power of 1024
If you just want one of them in a function:
SI
function fileSizeSI(bytes) {
const exponent = Math.floor(Math.log(bytes) / Math.log(1000.0))
const decimal = (bytes / Math.pow(1000.0, exponent)).toFixed(exponent ? 2 : 0)
return `${decimal} ${exponent ? `${'kMGTPEZY'[exponent - 1]}B` : 'B'}`
}
//kB,MB,GB,TB,PB,EB,ZB,YB
IEC
function fileSizeIEC(bytes) {
const exponent = Math.floor(Math.log(bytes) / Math.log(1024.0))
const decimal = (bytes / Math.pow(1024.0, exponent)).toFixed(exponent ? 2 : 0)
return `${decimal} ${exponent ? `${'kMGTPEZY'[exponent - 1]}iB` : 'B'}`
}
//KiB,MiB,GiB,TiB,PiB,EiB,ZiB,YiB
Usage:
console.log(fileSizeIEC(7412834521));
Upvotes: 40
Reputation: 2039
Another embodiment of the calculation
function humanFileSize(size) {
var i = size == 0 ? 0 : Math.floor(Math.log(size) / Math.log(1024));
return +((size / Math.pow(1024, i)).toFixed(2)) * 1 + ' ' + ['B', 'kB', 'MB', 'GB', 'TB'][i];
}
Upvotes: 192
Reputation: 282865
Here's another implementation, internationalized, written in TypeScript:
const UNITS = ['byte', 'kilobyte', 'megabyte', 'gigabyte', 'terabyte', 'petabyte']
const BYTES_PER_KB = 1000
/**
* Format bytes as human-readable text.
*
* @param sizeBytes Number of bytes.
*
* @return Formatted string.
*/
export function humanFileSize(sizeBytes: number | bigint): string {
let size = Math.abs(Number(sizeBytes))
let u = 0
while(size >= BYTES_PER_KB && u < UNITS.length-1) {
size /= BYTES_PER_KB
++u
}
return new Intl.NumberFormat([], {
style: 'unit',
unit: UNITS[u],
unitDisplay: 'short',
maximumFractionDigits: 1,
}).format(size)
}
Replace []
with a language code like fr
to force a localization other than the default.
console.log(humanFileSize(0))
console.log(humanFileSize(9))
console.log(humanFileSize(99))
console.log(humanFileSize(999))
console.log(humanFileSize(1000))
console.log(humanFileSize(1001))
console.log(humanFileSize(1023))
console.log(humanFileSize(1024))
console.log(humanFileSize(1025))
console.log(humanFileSize(100_000))
console.log(humanFileSize(1_000_000))
console.log(humanFileSize(1_000_000_000))
console.log(humanFileSize(1_000_000_000_000))
console.log(humanFileSize(1_000_000_000_000_000))
console.log(humanFileSize(1_000_000_000_000_000_000))
// fr
0 o
9 o
99 o
999 o
1 ko
1 ko
1 ko
1 ko
1 ko
100 ko
1 Mo
1 Go
1 To
1 Po
1 000 Po
// en-US
0 byte
9 byte
99 byte
999 byte
1 kB
1 kB
1 kB
1 kB
1 kB
100 kB
1 MB
1 GB
1 TB
1 PB
1,000 PB
You can get Intl.NumberFormat
to do the unit conversion for you automatically now. e.g.
const sizeFormatter = new Intl.NumberFormat([], {
style: 'unit',
unit: 'byte',
notation: "compact",
unitDisplay: "narrow",
})
console.log(sizeFormatter.format(0))
console.log(sizeFormatter.format(1))
console.log(sizeFormatter.format(999))
console.log(sizeFormatter.format(1000))
console.log(sizeFormatter.format(1023))
console.log(sizeFormatter.format(1024))
console.log(sizeFormatter.format(1024**2))
console.log(sizeFormatter.format(1024**3))
console.log(sizeFormatter.format(1024**4))
console.log(sizeFormatter.format(1024**5))
console.log(sizeFormatter.format(1024**6))
...but the units are kinda weird. e.g. 1024**4
is 1.1BB
which I guess is "billion bytes"; I don't think anyone ever uses that even if it's technically correct.
Upvotes: 13
Reputation: 1833
I wrote a size conversion function that also accepts human readable formats.
JS
const bitBase = 8;
const suffixes = {
bit: 'b',
b: 'B',
kb: 'KB',
mb: 'MB',
gb: 'GB',
tb: 'TB',
};
const multipliers = {
bit: {
toBitHr: 1,
toB: 1 / bitBase,
toKB: 1 / (bitBase * 1e3),
toMB: 1 / (bitBase * 1e6),
toGB: 1 / (bitBase * 1e9),
toTB: 1 / (bitBase * 1e12),
},
B: {
toBit: bitBase,
toBHr: 1,
toKB: 1 / 1e3,
toMB: 1 / 1e6,
toGB: 1 / 1e9,
toTB: 1 / 1e12,
},
KB: {
toBit: 1 / (bitBase * 1e3),
toB: 1e3,
toKBHr: 1,
toMB: 1 / 1e3,
toGB: 1 / 1e6,
toTB: 1 / 1e9,
},
MB: {
toBit: bitBase * 1e6,
toB: 1e6,
toKB: 1e3,
toMBHr: 1,
toGB: 1 / 1e3,
toTB: 1 / 1e6,
},
GB: {
toBit: bitBase * 1e9,
toB: 1e9,
toKB: 1e6,
toMB: 1e3,
toGBHr: 1,
toTB: 1 / 1e3,
},
TB: {
toBit: bitBase * 1e12,
toB: 1e12,
toKB: 1e9,
toMB: 1e6,
toGB: 1e3,
toTBHr: 1,
},
};
const round = (num, decimalPlaces) => {
const strNum = num.toString();
const isExp = strNum.includes('e');
if (isExp) {
return Number(num.toPrecision(decimalPlaces + 1));
}
return Number(
`${Math.round(Number(`${num}e${decimalPlaces}`))}e${decimalPlaces * -1}`,
);
};
function conv(
value,
hr,
rnd,
multiplier,
suffix,
) {
let val = value * multiplier;
if ((value * multiplier) > Number.MAX_SAFE_INTEGER) {
val = Number.MAX_SAFE_INTEGER;
}
if (val < Number.MIN_VALUE) val = 0;
if ((rnd || rnd === 0) && val < Number.MAX_SAFE_INTEGER) {
val = round(val, rnd);
}
if (hr) return `${val}${suffix}`;
return val;
}
const MemConv = (function _() {
return {
bit(value) {
return {
toBitHr(opts = {}) {
return conv(
value,
true,
opts.round || false,
multipliers.bit.toBitHr,
suffixes.bit,
);
},
toB(opts = {}) {
return conv(
value,
opts.hr || false,
opts.round || false,
multipliers.bit.toB,
suffixes.b,
);
},
toKB(opts = {}) {
return conv(
value,
opts.hr || false,
opts.round || false,
multipliers.bit.toKB,
suffixes.kb,
);
},
toMB(opts = {}) {
return conv(
value,
opts.hr || false,
opts.round || false,
multipliers.bit.toMB,
suffixes.mb,
);
},
toGB(opts = {}) {
return conv(
value,
opts.hr || false,
opts.round || false,
multipliers.bit.toGB,
suffixes.gb,
);
},
toTB(opts = {}) {
return conv(
value,
opts.hr || false,
opts.round || false,
multipliers.bit.toTB,
suffixes.tb,
);
},
};
},
B(value) {
return {
toBit(opts = {}) {
return conv(
value,
opts.hr || false,
opts.round || false,
multipliers.B.toBit,
suffixes.bit,
);
},
toBHr(opts = {}) {
return conv(
value,
true,
opts.round || false,
multipliers.B.toBHr,
suffixes.b,
);
},
toKB(opts = {}) {
return conv(
value,
opts.hr || false,
opts.round || false,
multipliers.B.toKB,
suffixes.kb,
);
},
toMB(opts = {}) {
return conv(
value,
opts.hr || false,
opts.round || false,
multipliers.B.toMB,
suffixes.mb,
);
},
toGB(opts = {}) {
return conv(
value,
opts.hr || false,
opts.round || false,
multipliers.B.toGB,
suffixes.gb,
);
},
toTB(opts = {}) {
return conv(
value,
opts.hr || false,
opts.round || false,
multipliers.B.toTB,
suffixes.tb,
);
},
};
},
KB(value) {
return {
toBit(opts = {}) {
return conv(
value,
opts.hr || false,
opts.round || false,
multipliers.KB.toBit,
suffixes.bit,
);
},
toB(opts = {}) {
return conv(
value,
opts.hr || false,
opts.round || false,
multipliers.KB.toB,
suffixes.b,
);
},
toKBHr(opts = {}) {
return conv(
value,
true,
opts.round || false,
multipliers.KB.toKBHr,
suffixes.kb,
);
},
toMB(opts = {}) {
return conv(
value,
opts.hr || false,
opts.round || false,
multipliers.KB.toMB,
suffixes.mb,
);
},
toGB(opts = {}) {
return conv(
value,
opts.hr || false,
opts.round || false,
multipliers.KB.toGB,
suffixes.gb,
);
},
toTB(opts = {}) {
return conv(
value,
opts.hr || false,
opts.round || false,
multipliers.KB.toTB,
suffixes.tb,
);
},
};
},
MB(value) {
return {
toBit(opts = {}) {
return conv(
value,
opts.hr || false,
opts.round || false,
multipliers.MB.toBit,
suffixes.bit,
);
},
toB(opts = {}) {
return conv(
value,
opts.hr || false,
opts.round || false,
multipliers.MB.toB,
suffixes.b,
);
},
toKB(opts = {}) {
return conv(
value,
opts.hr || false,
opts.round || false,
multipliers.MB.toKB,
suffixes.kb,
);
},
toMBHr(opts = {}) {
return conv(
value,
true,
opts.round || false,
multipliers.MB.toMBHr,
suffixes.mb,
);
},
toGB(opts = {}) {
return conv(
value,
opts.hr || false,
opts.round || false,
multipliers.MB.toGB,
suffixes.gb,
);
},
toTB(opts = {}) {
return conv(
value,
opts.hr || false,
opts.round || false,
multipliers.MB.toTB,
suffixes.tb,
);
},
};
},
GB(value) {
return {
toBit(opts = {}) {
return conv(
value,
opts.hr || false,
opts.round || false,
multipliers.GB.toBit,
suffixes.bit,
);
},
toB(opts = {}) {
return conv(
value,
opts.hr || false,
opts.round || false,
multipliers.GB.toB,
suffixes.b,
);
},
toKB(opts = {}) {
return conv(
value,
opts.hr || false,
opts.round || false,
multipliers.GB.toKB,
suffixes.kb,
);
},
toMB(opts = {}) {
return conv(
value,
opts.hr || false,
opts.round || false,
multipliers.GB.toMB,
suffixes.mb,
);
},
toGBHr(opts = {}) {
return conv(
value,
true,
opts.round || false,
multipliers.GB.toGBHr,
suffixes.gb,
);
},
toTB(opts = {}) {
return conv(
value,
opts.hr || false,
opts.round || false,
multipliers.GB.toTB,
suffixes.tb,
);
},
};
},
TB(value) {
return {
toBit(opts = {}) {
return conv(
value,
opts.hr || false,
opts.round || false,
multipliers.TB.toBit,
suffixes.bit,
);
},
toB(opts = {}) {
return conv(
value,
opts.hr || false,
opts.round || false,
multipliers.TB.toB,
suffixes.b,
);
},
toKB(opts = {}) {
return conv(
value,
opts.hr || false,
opts.round || false,
multipliers.TB.toKB,
suffixes.kb,
);
},
toMB(opts = {}) {
return conv(
value,
opts.hr || false,
opts.round || false,
multipliers.TB.toMB,
suffixes.mb,
);
},
toGB(opts = {}) {
return conv(
value,
opts.hr || false,
opts.round || false,
multipliers.TB.toGB,
suffixes.gb,
);
},
toTBHr(opts = {}) {
return conv(
value,
true,
opts.round || false,
multipliers.TB.toTBHr,
suffixes.tb,
);
},
};
},
};
}());
const testCases = [1, 10, 150, 1000, 74839.67346];
const HRSuffixes = Object.values(suffixes);
const roundDecimals = 2;
const precision = Number(`0.${'0'.repeat(roundDecimals)}5`);
const SCIENTIFIC_NOT_NUMBER_REGXP = /[-+]?[0-9]*.?[0-9]+([eE][-+]?[0-9]+)?/g;
const SUFFIX_REGXP = /[a-z]+$/i;
const CONVERSION_TO_REGXP = /(?<=to).*(?=hr+$)|(?<=to).*(?=hr+$)?/i;
for (const conversionFrom of (Object.keys(MemConv))) {
for (const tCase of testCases) {
const convFunc = MemConv[conversionFrom](tCase);
for (const [conversionToFn, f] of Object.entries(convFunc)) {
const conversionTo = (conversionToFn.match(CONVERSION_TO_REGXP) || [conversionToFn])[0];
const result = f();
const humanReadable = f({ hr: true });
const rounded = f({ round: roundDecimals });
const roundedAndHumanReadable = f({ hr: true, round: roundDecimals });
console.log({
value: tCase,
from: conversionFrom,
to: conversionTo,
result,
humanReadable,
rounded,
roundedAndHumanReadable,
});
}
}
}
test
import assert from 'assert';
function test() {
const testCases = [1, 10, 150, 1000, 74839.67346];
const HRSuffixes = Object.values(suffixes);
const roundDecimals = 2;
const precision = Number(`0.${'0'.repeat(roundDecimals)}5`);
const SCIENTIFIC_NOT_NUMBER_REGXP = /[-+]?[0-9]*.?[0-9]+([eE][-+]?[0-9]+)?/g;
const SUFFIX_REGXP = /[a-z]+$/i;
const CONVERSION_TO_REGXP = /(?<=to).*(?=hr+$)|(?<=to).*(?=hr+$)?/i;
for (const conversionFrom of (Object.keys(MemConv) as (keyof typeof MemConv)[])) {
for (const tCase of testCases) {
const convFunc = MemConv[conversionFrom](tCase);
for (const [conversionToFn, f] of Object.entries(convFunc)) {
const conversionTo = (conversionToFn.match(CONVERSION_TO_REGXP) || [conversionToFn])[0];
const expectedSuffix = suffixes[conversionTo.toLowerCase() as keyof typeof suffixes];
const multiplier = multipliers[conversionFrom][conversionToFn as keyof typeof multipliers[typeof conversionFrom]];
const expectedResult = tCase * multiplier > Number.MAX_SAFE_INTEGER
? Number.MAX_SAFE_INTEGER
: tCase * multiplier;
const result = f();
const humanReadable = f({ hr: true });
const rounded = f({ round: roundDecimals });
const roundedAndHumanReadable = f({ hr: true, round: roundDecimals });
const resHrNumber = Number((humanReadable.match(SCIENTIFIC_NOT_NUMBER_REGXP) || [''])[0]);
const resHrSuffix = (humanReadable.match(SUFFIX_REGXP) || [0])[0];
const resRoundHrNumber = Number((roundedAndHumanReadable.match(SCIENTIFIC_NOT_NUMBER_REGXP) || [''])[0]);
const resRoundHrSuffix = (roundedAndHumanReadable.match(SUFFIX_REGXP) || [0])[0];
if (/hr$/i.test(conversionToFn)) {
const resNumber = Number((humanReadable.match(SCIENTIFIC_NOT_NUMBER_REGXP) || [''])[0]);
const resSuffix = (humanReadable.match(SUFFIX_REGXP) || [0])[0];
assert(typeof result === 'string');
assert(typeof resSuffix === 'string');
assert(typeof resRoundHrNumber === 'number');
assert(typeof rounded === 'string');
assert(result === humanReadable);
assert(resSuffix === expectedSuffix);
assert(resNumber <= expectedResult + precision && resNumber >= expectedResult - precision);
} else {
assert(typeof result === 'number');
assert(result === resHrNumber);
assert(typeof rounded === 'number');
assert(result <= expectedResult + precision && result >= expectedResult - precision);
}
console.log({
value: tCase,
from: conversionFrom,
to: conversionToFn,
result,
humanReadable,
rounded,
roundedAndHumanReadable,
});
assert(typeof resHrSuffix === 'string');
assert(typeof resHrNumber === 'number');
assert(resHrSuffix === expectedSuffix);
assert(resHrSuffix === resRoundHrSuffix);
assert(HRSuffixes.includes(resHrSuffix));
}
}
}
}
test();
Usage
// GB to GB humanReadable
console.log(MemConv.GB(11.1942).toGBHr()); // 11.1942GB;
// GB to MB
console.log(MemConv.GB(11.1942).toMB());// 11194.2;
// MB to MB humanReadable
console.log(MemConv.MB(11.1942).toGB({ hr: true }));// 0.011194200000000001GB;
// MB to MB humanReadable with rounding
console.log(MemConv.MB(11.1942).toGB({ hr: true, round: 3 }));// 0.011GB;
Upvotes: 0
Reputation: 807
i'm just 10 years late! for es6
function humanReadableSize(bytes) {
let size = parseInt(data)
for (let unit of ['b', 'Kb', 'Mb', 'Gb']) {
if (size < 1024) return `${size.toFixed(2)} ${unit}`
size /= 1024.0
}
}
Upvotes: 3
Reputation: 1001
The typescript version to @Andrew V answer with the new "Template Literal Types"
export const humanFileSize = (bytes: number): `${number} ${'B' | 'KB' | 'MB' | 'GB' | 'TB'}` => {
const index = Math.floor(Math.log(bytes) / Math.log(1024));
return `${Number((bytes / Math.pow(1024, index)).toFixed(2)) * 1} ${(['B', 'KB', 'MB', 'GB', 'TB'] as const)[index]}`;
};
Upvotes: 1
Reputation: 19
To have the number of decimals dynamically adjusted in the voted solution, convert the bytes.toFixed(dp)
to number and then back to string like this:
return Number(bytes.toFixed(dp)).toString() + " " + units[u];
This will show 100 GiB instead of 100.00 GiB. Reference to question toFixed() dynamically in js
Upvotes: 0
Reputation: 5547
A simple and short "Pretty Bytes" function for the SI system without the unnecessary fractionals rounding.
In fact, because the number size is supposed to be human-readable, a "1 of a thousand fraction" display is no longer human.
The number of decimal places is defaulted to 2 but can be modified on calling the function to other values. The common mostly display is the default 2 decimal place.
The code is short and uses the method of Number String Triplets.
// Simple Pretty Bytes with SI system
// Without fraction rounding
function numberPrettyBytesSI(Num=0, dec=2){
if (Num<1000) return Num+" Bytes";
Num =("0".repeat((Num+="").length*2%3)+Num).match(/.{3}/g);
return Number(Num[0])+"."+Num[1].substring(0,dec)+" "+" kMGTPEZY"[Num.length]+"B";
}
console.log(numberPrettyBytesSI(0));
console.log(numberPrettyBytesSI(500));
console.log(numberPrettyBytesSI(1000));
console.log(numberPrettyBytesSI(15000));
console.log(numberPrettyBytesSI(12345));
console.log(numberPrettyBytesSI(123456));
console.log(numberPrettyBytesSI(1234567));
console.log(numberPrettyBytesSI(12345678));
Upvotes: 5
Reputation: 282865
Here's one I wrote:
/**
* Format bytes as human-readable text.
*
* @param bytes Number of bytes.
* @param si True to use metric (SI) units, aka powers of 1000. False to use
* binary (IEC), aka powers of 1024.
* @param dp Number of decimal places to display.
*
* @return Formatted string.
*/
function humanFileSize(bytes, si=false, dp=1) {
const thresh = si ? 1000 : 1024;
if (Math.abs(bytes) < thresh) {
return bytes + ' B';
}
const units = si
? ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
let u = -1;
const r = 10**dp;
do {
bytes /= thresh;
++u;
} while (Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1);
return bytes.toFixed(dp) + ' ' + units[u];
}
console.log(humanFileSize(1551859712)) // 1.4 GiB
console.log(humanFileSize(5000, true)) // 5.0 kB
console.log(humanFileSize(5000, false)) // 4.9 KiB
console.log(humanFileSize(-10000000000000000000000000000)) // -8271.8 YiB
console.log(humanFileSize(999949, true)) // 999.9 kB
console.log(humanFileSize(999950, true)) // 1.0 MB
console.log(humanFileSize(999950, true, 2)) // 999.95 kB
console.log(humanFileSize(999500, true, 0)) // 1 MB
Upvotes: 593
Reputation: 2373
There are lots of great answers here. But if your looking for a really simple way, and you don't mind a popular library, a great solution is filesize
https://www.npmjs.com/package/filesize
It has lots of options and the usage is simple e.g.
filesize(265318); // "259.1 KB"
Taken from their excellent examples
Upvotes: 10
Reputation: 1125
As of 2020, you can use file-size npm package, that supports formatting in IEC (power 1024, default), SI (power 1000), and JEDEC (Alternative SI Unit Notation).
npm install file-size
import filesize from "filesize";
// outputs: 186.46 MB
filesize(186457865).human('si');
// outputs: 177.82 MiB
filesize(186457865).human();
https://www.npmjs.com/package/file-size
Upvotes: 8
Reputation: 92377
This is size improvement of mpen answer
function humanFileSize(bytes, si=false) {
let u, b=bytes, t= si ? 1000 : 1024;
['', si?'k':'K', ...'MGTPEZY'].find(x=> (u=x, b/=t, b**2<1));
return `${u ? (t*b).toFixed(1) : bytes} ${u}${!si && u ? 'i':''}B`;
}
function humanFileSize(bytes, si=false) {
let u, b=bytes, t= si ? 1000 : 1024;
['', si?'k':'K', ...'MGTPEZY'].find(x=> (u=x, b/=t, b**2<1));
return `${u ? (t*b).toFixed(1) : bytes} ${u}${!si && u ? 'i':''}B`;
}
// TEST
console.log(humanFileSize(5000)); // 4.9 KiB
console.log(humanFileSize(5000,true)); // 5.0 kB
Upvotes: 1
Reputation: 1456
My answer might be late, but I guess it will help someone.
/**
* Format file size in metric prefix
* @param fileSize
* @returns {string}
*/
const formatFileSizeMetric = (fileSize) => {
let size = Math.abs(fileSize);
if (Number.isNaN(size)) {
return 'Invalid file size';
}
if (size === 0) {
return '0 bytes';
}
const units = ['bytes', 'kB', 'MB', 'GB', 'TB'];
let quotient = Math.floor(Math.log10(size) / 3);
quotient = quotient < units.length ? quotient : units.length - 1;
size /= (1000 ** quotient);
return `${+size.toFixed(2)} ${units[quotient]}`;
};
/**
* Format file size in binary prefix
* @param fileSize
* @returns {string}
*/
const formatFileSizeBinary = (fileSize) => {
let size = Math.abs(fileSize);
if (Number.isNaN(size)) {
return 'Invalid file size';
}
if (size === 0) {
return '0 bytes';
}
const units = ['bytes', 'kiB', 'MiB', 'GiB', 'TiB'];
let quotient = Math.floor(Math.log2(size) / 10);
quotient = quotient < units.length ? quotient : units.length - 1;
size /= (1024 ** quotient);
return `${+size.toFixed(2)} ${units[quotient]}`;
};
Examples:
// Metrics prefix
formatFileSizeMetric(0) // 0 bytes
formatFileSizeMetric(-1) // 1 bytes
formatFileSizeMetric(100) // 100 bytes
formatFileSizeMetric(1000) // 1 kB
formatFileSizeMetric(10**5) // 10 kB
formatFileSizeMetric(10**6) // 1 MB
formatFileSizeMetric(10**9) // 1GB
formatFileSizeMetric(10**12) // 1 TB
formatFileSizeMetric(10**15) // 1000 TB
// Binary prefix
formatFileSizeBinary(0) // 0 bytes
formatFileSizeBinary(-1) // 1 bytes
formatFileSizeBinary(1024) // 1 kiB
formatFileSizeBinary(2048) // 2 kiB
formatFileSizeBinary(2**20) // 1 MiB
formatFileSizeBinary(2**30) // 1 GiB
formatFileSizeBinary(2**40) // 1 TiB
formatFileSizeBinary(2**50) // 1024 TiB
Upvotes: 6
Reputation: 42497
I found @cocco's answer interesting, but had the following issues with it:
TypeScript:
/**
* Describes manner by which a quantity of bytes will be formatted.
*/
enum ByteFormat {
/**
* Use Base 10 (1 kB = 1000 bytes). Recommended for sizes of files on disk, disk sizes, bandwidth.
*/
SI = 0,
/**
* Use Base 2 (1 KiB = 1024 bytes). Recommended for RAM size, size of files on disk.
*/
IEC = 1
}
/**
* Returns a human-readable representation of a quantity of bytes in the most reasonable unit of magnitude.
* @example
* formatBytes(0) // returns "0 bytes"
* formatBytes(1) // returns "1 byte"
* formatBytes(1024, ByteFormat.IEC) // returns "1 KiB"
* formatBytes(1024, ByteFormat.SI) // returns "1.02 kB"
* @param size The size in bytes.
* @param format Format using SI (Base 10) or IEC (Base 2). Defaults to SI.
* @returns A string describing the bytes in the most reasonable unit of magnitude.
*/
function formatBytes(
value: number,
format: ByteFormat = ByteFormat.SI
) {
const [multiple, k, suffix] = (format === ByteFormat.SI
? [1000, 'k', 'B']
: [1024, 'K', 'iB']) as [number, string, string]
// tslint:disable-next-line: no-bitwise
const exp = (Math.log(value) / Math.log(multiple)) | 0
// or, if you'd prefer not to use bitwise expressions or disabling tslint rules, remove the line above and use the following:
// const exp = value === 0 ? 0 : Math.floor(Math.log(value) / Math.log(multiple))
const size = Number((value / Math.pow(multiple, exp)).toFixed(2))
return (
size +
' ' +
(exp
? (k + 'MGTPEZY')[exp - 1] + suffix
: 'byte' + (size !== 1 ? 's' : ''))
)
}
// example
[0, 1, 1024, Math.pow(1024, 2), Math.floor(Math.pow(1024, 2) * 2.34), Math.pow(1024, 3), Math.floor(Math.pow(1024, 3) * 892.2)].forEach(size => {
console.log('Bytes: ' + size)
console.log('SI size: ' + formatBytes(size))
console.log('IEC size: ' + formatBytes(size, 1) + '\n')
});
Upvotes: 2
Reputation: 37898
I wanted the "file manager" behavior (e.g., Windows Explorer) where the number of decimal places is proportional to the number size. Seemingly none of the other answers does this.
function humanFileSize(size) {
if (size < 1024) return size + ' B'
let i = Math.floor(Math.log(size) / Math.log(1024))
let num = (size / Math.pow(1024, i))
let round = Math.round(num)
num = round < 10 ? num.toFixed(2) : round < 100 ? num.toFixed(1) : round
return `${num} ${'KMGTPEZY'[i-1]}B`
}
Here's some examples:
humanFileSize(0) // "0 B"
humanFileSize(1023) // "1023 B"
humanFileSize(1024) // "1.00 KB"
humanFileSize(10240) // "10.0 KB"
humanFileSize(102400) // "100 KB"
humanFileSize(1024000) // "1000 KB"
humanFileSize(12345678) // "11.8 MB"
humanFileSize(1234567890) // "1.15 GB"
Upvotes: 8
Reputation: 2646
For those who use Angular
, there's a package called angular-pipes
that has a pipe for this:
File
import { BytesPipe } from 'angular-pipes';
Usage
{{ 150 | bytes }} <!-- 150 B -->
{{ 1024 | bytes }} <!-- 1 KB -->
{{ 1048576 | bytes }} <!-- 1 MB -->
{{ 1024 | bytes: 0 : 'KB' }} <!-- 1 MB -->
{{ 1073741824 | bytes }} <!-- 1 GB -->
{{ 1099511627776 | bytes }} <!-- 1 TB -->
{{ 1073741824 | bytes : 0 : 'B' : 'MB' }} <!-- 1024 MB -->
Upvotes: 0
Reputation: 335
let bytes = 1024 * 10 * 10 * 10;
console.log(getReadableFileSizeString(bytes))
will return 1000.0Кб instead of 1MB
Upvotes: -3
Reputation: 584
Solution as ReactJS Component
Bytes = React.createClass({
formatBytes() {
var i = Math.floor(Math.log(this.props.bytes) / Math.log(1024));
return !this.props.bytes && '0 Bytes' || (this.props.bytes / Math.pow(1024, i)).toFixed(2) + " " + ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'][i]
},
render () {
return (
<span>{ this.formatBytes() }</span>
);
}
});
UPDATE For those using es6 here is a stateless version of this same component
const sufixes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
const getBytes = (bytes) => {
const i = Math.floor(Math.log(bytes) / Math.log(1024));
return !bytes && '0 Bytes' || (bytes / Math.pow(1024, i)).toFixed(2) + " " + sufixes[i];
};
const Bytes = ({ bytes }) => (<span>{ getBytes(bytes) }</span>);
Bytes.propTypes = {
bytes: React.PropTypes.number,
};
Upvotes: 17
Reputation: 1768
Another example similar to those here
function fileSize(b) {
var u = 0, s=1024;
while (b >= s || -b >= s) {
b /= s;
u++;
}
return (u ? b.toFixed(1) + ' ' : b) + ' KMGTPEZY'[u] + 'B';
}
It measures negligibly better performance than the others with similar features.
Upvotes: 14
Reputation: 11228
Based on cocco's answer but slightly desugerified (honestly, ones I was comfortable with are remained/added) and doesn't show trailing zeros but still supports 0, hope to be useful for others:
function fileSizeSI(size) {
var e = (Math.log(size) / Math.log(1e3)) | 0;
return +(size / Math.pow(1e3, e)).toFixed(2) + ' ' + ('kMGTPEZY'[e - 1] || '') + 'B';
}
// test:
document.write([0, 23, 4322, 324232132, 22e9, 64.22e12, 76.22e15, 64.66e18, 77.11e21, 22e24].map(fileSizeSI).join('<br>'));
Upvotes: 4
Reputation: 840
Here's mine - works for really big files too -_-
function formatFileSize(size)
{
var sizes = [' Bytes', ' KB', ' MB', ' GB', ' TB', ' PB', ' EB', ' ZB', ' YB'];
for (var i = 1; i < sizes.length; i++)
{
if (size < Math.pow(1024, i)) return (Math.round((size/Math.pow(1024, i-1))*100)/100) + sizes[i-1];
}
return size;
}
Upvotes: 4
Reputation: 10356
sizeOf = function (bytes) {
if (bytes == 0) { return "0.00 B"; }
var e = Math.floor(Math.log(bytes) / Math.log(1024));
return (bytes/Math.pow(1024, e)).toFixed(2)+' '+' KMGTP'.charAt(e)+'B';
}
sizeOf(2054110009);
//=> "1.91 GB"sizeOf(7054110);
//=> "6.73 MB"sizeOf( (3*1024*1024) );
//=> "3.00 MB"
Upvotes: 26
Reputation: 1515
Based on cocco's idea, here's a less compact -but hopefully more comprehensive- example.
<!DOCTYPE html>
<html>
<head>
<title>File info</title>
<script>
<!--
function fileSize(bytes) {
var exp = Math.log(bytes) / Math.log(1024) | 0;
var result = (bytes / Math.pow(1024, exp)).toFixed(2);
return result + ' ' + (exp == 0 ? 'bytes': 'KMGTPEZY'[exp - 1] + 'B');
}
function info(input) {
input.nextElementSibling.textContent = fileSize(input.files[0].size);
}
-->
</script>
</head>
<body>
<label for="upload-file"> File: </label>
<input id="upload-file" type="file" onchange="info(this)">
<div></div>
</body>
</html>
Upvotes: 11
Reputation: 17825
1551859712 / 1024 = 1515488
1515488 / 1024 = 1479.96875
1479.96875 / 1024 = 1.44528198242188
Your solution is correct. The important thing to realize is that in order to get from 1551859712
to 1.5
, you have to do divisions by 1000, but bytes are counted in binary-to-decimal chunks of 1024, hence why the Gigabyte value is less.
Upvotes: 3
Reputation: 55392
It depends on whether you want to use the binary or decimal convention.
RAM, for instance, is always measured in binary, so to express 1551859712 as ~1.4GiB would be correct.
On the other hand, hard disk manufacturers like to use decimal, so they would call it ~1.6GB.
And just to be confusing, floppy disks use a mixture of the two systems - their 1MB is actually 1024000 bytes.
Upvotes: 66