352

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?

Hristo
  • 45,559
  • 65
  • 163
  • 230
  • @Brendan... thanks! I appreciate that :) I'll be honest though... I didn't come up with this all by myself. I'm pretty sure I saw something similar somewhere at some point. – Hristo May 02 '12 at 19:39
  • your function will fail on anything larger than about 2^90. – Janus Troelsen Sep 28 '12 at 21:10
  • 1
    @JanusTroelsen... why is that? please give me some more details! – Hristo Sep 28 '12 at 21:36
  • 3
    getReadableFileSizeString(0); returns 0.1kb ;p – Daniel Magnusson Dec 10 '12 at 15:08
  • 3
    Why should it be 1.5? It's `1.445281982421875` which correctly rounds down to 1.4. – mpen Feb 17 '13 at 09:00
  • 1
    1551859712/(1024^3)=1.445281982421875 which is correct! – H.M. Jan 11 '14 at 09:09
  • 6
    I love that you added `YB`. Doubtful anyone will get even 1 YB for his DB. It will cost [100 trillion dollars](https://gizmodo.com/5557676/how-much-money-would-a-yottabyte-hard-drive-cost)! – Guy Jan 10 '19 at 08:49
  • 13
    @guyarad - there is a famous picture of a 5MB hard drive from 50 years ago (was at the size of a room and weighed about a ton). i'm sure back then they didn't even dream about GB and TB, and look at where we are today... never say never ;-) – TheCuBeMan Oct 15 '19 at 13:30

25 Answers25

548

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
mpen
  • 272,448
  • 266
  • 850
  • 1,236
  • 2
    I'm making one adjustment: When evaluating the threshold, take the absolute value. This way the function will support negative values. Nice function! Thank you for not using a switch statement!! – Aaron Blenkush May 02 '14 at 15:33
  • 31
    @AaronBlenkush: When would you have a negative file size? – mpen May 02 '14 at 15:41
  • 29
    I just copied your function into a Google Sheet I'm using to show size delta after a "cleanup" operation. Before, After, and Diff. The cleanup operation resulted in the growth of some database tables, and the reduction in others. For example, Table A has a diff of -1.95 MB, while Table B has a diff of 500 kB. Therefore: positive and negative :-) – Aaron Blenkush May 02 '14 at 15:46
  • 1
    Here is the compressed version of the script: `function humanFileSize(B,i){var e=i?1e3:1024;if(Math.abs(B)=e&&t – randers Jun 02 '15 at 07:28
  • 6
    @RAnders00: Thanks for the minified version. Can you tell me, though, why you inserted the two invisible Unicode characters U+200C (ZERO WIDTH NON-JOINER) and U+200B (ZERO WIDTH SPACE) after the *E* oft *EiB*? Is this intended to be a watermark, so that you can track who used this code? If so, I think you should have made that transparent in your post. – Leviathan Apr 28 '16 at 12:01
  • 1
    @Leviathan I didn't insert them on purpose. I just ran it through UglifyJS and pasted it here, so I'm not sure why these characters are even there... – randers Apr 28 '16 at 12:05
  • @mpen when you calculate a disk space delta for example. – OverCoder Oct 18 '18 at 06:06
  • @Frosty Z: The correct SI prefix is lowercase `k`. Source: http://mathworld.wolfram.com/SIPrefixes.html – mpen Apr 25 '19 at 02:56
  • @mpen sorry you're right. According to Wikipedia article on "kilobyte", it's still uppercase K for KiB. Counter intuitive... – Maxime Pacary Apr 25 '19 at 06:40
  • `humanFileSize(999990,true);` returns `1000.0 kB` instead of `1.0 MB`. So I added `bytes = parseFloat(bytes.toFixed(1));` after `++u;` – elipoultorak May 18 '20 at 11:04
  • 1
    @elipoultorak You're rounding on every iteration which will create an increasing error margin. I've updated the answer with an alternative fix. Thanks for pointing that out! – mpen May 19 '20 at 01:57
  • @mpen I like what you did better, but as a side point, what's the problem with having a small error margin if you're going to divide it by 1000 or 1024 anyway? – elipoultorak May 20 '20 at 10:19
  • @elipoultorak Probably not a big deal at all. – mpen May 20 '20 at 17:18
  • 1
    I see that you updated to code to add `dp`. Could you please explain what is it? – vee Nov 05 '20 at 09:25
  • 2
    @vee Decimal places. You can see some examples with different numbers at the bottom. I'll update the code with a doc-comment. – mpen Nov 05 '20 at 19:34
  • Cool variables names, bro... – James Bond Sep 14 '22 at 15:42
165

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];
}
Arainty
  • 91
  • 9
Andrew V.
  • 1,769
  • 1
  • 11
  • 5
  • 10
    seems doesn't handle 0 – Offirmo Aug 29 '14 at 23:53
  • 5
    It does or does not handle 0? After all, this with a if(size == 0) {} else {} is still more elegant than most I've seen. – Rodrigo Aug 30 '15 at 15:02
  • 23
    Changing the first line to `var i = size == 0 ? 0 : Math.floor( Math.log(size) / Math.log(1024) );` seems to do the trick if it's 0. It will return "0 B". – Gavin Aug 15 '17 at 07:44
  • 2
    Just FYI; I know the answer is plain JavaScript, but if someone wan't to use it in TypeScript, it doesn't work (not typed correctly, as you're doing `toFixed` and then doing math with a string. What does the `* 1` do? – Frexuz Dec 12 '19 at 06:00
  • 13
    The `*1` changes the data type from string to number, so for the value `1024` you get `1 kB` instead of `1.00 kB`. You can make TypeScript happy by doing `Number((size / Math.pow(1024, i)).toFixed(2))` to accomplish the same thing. – Adrian Theodorescu Dec 12 '19 at 22:10
66

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.

Neil
  • 54,642
  • 8
  • 60
  • 72
  • 7
    supper funny ;-) "just to be confusing, floppy disks use a mixture of the two systems - their 1MB is actually 1024000 bytes." – FranXho May 03 '18 at 07:12
  • 2
    true, RAM sizes are measured using IEC units, disk sizes using metric.. there's an isomorphic npm module to convert both: [byte-size](https://github.com/75lb/byte-size) – Lloyd Oct 26 '19 at 09:42
37

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 short oneliner function:

SI

function fileSizeSI(a,b,c,d,e){
 return (b=Math,c=b.log,d=1e3,e=c(a)/c(d)|0,a/b.pow(d,e)).toFixed(2)
 +' '+(e?'kMGTPEZY'[--e]+'B':'Bytes')
}
//kB,MB,GB,TB,PB,EB,ZB,YB

IEC

function fileSizeIEC(a,b,c,d,e){
 return (b=Math,c=b.log,d=1024,e=c(a)/c(d)|0,a/b.pow(d,e)).toFixed(2)
 +' '+(e?'KMGTPEZY'[--e]+'iB':'Bytes')
}
//KiB,MiB,GiB,TiB,PiB,EiB,ZiB,YiB

Usage:

console.log(fileSizeIEC(7412834521));

if you have some questions about the functions just ask

cocco
  • 16,442
  • 7
  • 62
  • 77
  • very nice compact code, I'd personally add a couple of extra chars for control of the decimal places though. – Orwellophile Sep 21 '15 at 04:58
  • Hi! Actually the code is how i wrote it the first time in jsfiddle. In the last years i learned myself to use shorthand and bitwise. Slow mobile devices, slow internet, not much space... doing so i saved much time. But thats not all, the overall perfromance increased in every browser drastically and the whole code loads much faster ... i don't use jquery so i don't have to load 100kb everytime. I also need to say that i write javascript also in microcontrollers, Smart TV's, game consoles. those have limited space(MCU's), performance(SmartTV's) and naturally sometimes slow connnection(Mobile) – cocco Dec 11 '15 at 17:27
  • Said that i hope you understand my choice. All i can do is explain what you don't understand or at the other side i'm always happy to learn new things. If there is something in my code that could increase the performance or save space i'm happy to hear it. – cocco Dec 11 '15 at 17:29
  • Another point is that on many questions there is already a nice readable jquery solution. Sometimes you find no native javascript... in that case i always like to post the "vanilla" solution. Using pure old js. which most of the time is not much longer than the js way. Btw in this specific answer i just wanted to avoid LOOPS.... i'm not even shure if it's faster than the other functions. Theoretically it's faster ... but it uses pow & log which could slow down everything. – cocco Dec 11 '15 at 17:37
  • I should also mention that i always cache things like 'window.document' or 'math.something' in a global var as i need them many times in various functions. Doing so i increase, most of all, the performance. Useless to say that also space is saved. "d=1e3,e=log(a)/log(d)|0,a/pow(d,e)" .... b & c not needed anymore. – cocco Dec 11 '15 at 17:59
  • 31
    Minification should be part of your build process, not your coding style. No serious developer will use this code because of that as it takes too long to read and verify correctness. – huysentruitw May 31 '17 at 11:10
  • 1
    For those who hate to see "15.00 Bytes", you can just modified this part a bit: `.toFixed(e ? 2 : 0)` – Lukman Aug 06 '17 at 04:34
25
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"

Joshaven Potter
  • 10,308
  • 2
  • 18
  • 8
  • 2
    If you wanted to get rid of the extra space for bytes, you could use the zero width space `\u200b`: `'\u200bKMGTP'`. – cdmckay May 04 '18 at 11:07
17

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,
};
j2L4e
  • 6,914
  • 34
  • 40
  • 1
    Great, thanks. You just forgot "bytes" inside Math.log() in the first line of getBytes function – BaptWaels Apr 12 '17 at 17:14
  • 1
    Very nice. For disambiguation, and with ES6 notation, you might use this: return (!bytes && '0 Bytes') || `${(bytes / (1024 ** i)).toFixed(2)} ${suffixes[i]}`; – Little Brain Jun 01 '20 at 13:19
13

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.

Nick Kuznia
  • 1,698
  • 17
  • 27
  • 1
    This does provide better performance than some other answers. I'm using this. Some others made my Chrome tabs hang and take 99.9% CPU as I was doing a periodic calculation. – Nir Lanka Sep 07 '19 at 10:19
11

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> 
Community
  • 1
  • 1
KitKat
  • 1,495
  • 14
  • 15
8

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"
Camilo Martin
  • 37,236
  • 20
  • 111
  • 154
  • using toFixed converts it to a string, so your round is either a string or a number. this is bad practice, you can easily convert it back to a number: `+num.tofixed(2)` – Vincent Duprez Oct 28 '19 at 10:27
  • Does `.toPrecision(3)` not cover all these cases? Oh.. I guess it doesn't cover between 1000 and 1023. Bummer. – mpen May 19 '20 at 02:11
  • It shows `9.9` for `10130` input, but should show `9.89`. For `10239` – `10.0`, but should `9.99`. (Win 10 Explorer behavior) – KeyKi Aug 13 '20 at 05:21
  • `humanFileSize(10000000000000000000000000000) -> 8.08 undefinedB` ahh my favourite kind of byte – Sam Oct 06 '20 at 11:10
  • The most closed to Windows Explorer format function: https://gist.github.com/AlttiRi/ee82de3728624f997b38e4fb90906914 – KeyKi Dec 28 '21 at 03:13
8

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

adlerer
  • 1,010
  • 11
  • 14
  • 1
    Note: this library `file-size` is much less used and not really supported (last release 7yr ago). Suggest looking at `filesize` instead from my answer https://stackoverflow.com/a/65124044/4653517 – James Mudd Jul 21 '22 at 07:36
7

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

James Mudd
  • 1,816
  • 1
  • 20
  • 25
6

My answer might be late, but I guess it will help someone.

Metric prefix:

/**
 * 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]}`;
};

Binary prefix:

/**
 * 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
Community
  • 1
  • 1
Kerkouch
  • 1,446
  • 10
  • 14
6

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.

mpen
  • 272,448
  • 266
  • 850
  • 1,236
5

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));
Nimantha
  • 6,405
  • 6
  • 28
  • 69
Mohsen Alyafei
  • 4,765
  • 3
  • 30
  • 42
4

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;
}
fiffy
  • 840
  • 9
  • 16
  • It combines the performance hit of both the looping and the use of exponentiation, while being pretty hard to read. I don't really see the point. – spectras Aug 23 '15 at 16:38
  • 2
    Don't use it then. It's just clientside cpu thats used so who cares ;) – fiffy Aug 31 '15 at 08:31
  • 2
    @fiffy Well, client CPU is precious too, especially on mobile and with complex applications. :) – Raito Jul 06 '16 at 10:39
4

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>'));
Community
  • 1
  • 1
Ebrahim Byagowi
  • 10,338
  • 4
  • 70
  • 81
3
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.

Eli
  • 17,397
  • 4
  • 36
  • 49
  • @Eli... yea, it seems like it. I guess I was expecting "1.5" since its 1551859712, but that would mean I'm in decimal not binary. – Hristo May 02 '12 at 19:44
3

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
    }
}
rho
  • 771
  • 9
  • 24
2

I found @cocco's answer interesting, but had the following issues with it:

  1. Don't modify native types or types you don't own
  2. Write clean, readable code for humans, let minifiers optimize code for machines
  3. (Bonus for TypeScript users) Doesn't play well with TypeScript

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')
});
moribvndvs
  • 42,191
  • 11
  • 135
  • 149
1

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
Kamil Kiełczewski
  • 85,173
  • 29
  • 368
  • 345
1

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]}`;
};
Israel kusayev
  • 833
  • 8
  • 20
0

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 -->

Link to the docs.

Sinandro
  • 2,426
  • 3
  • 21
  • 36
0

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

Robert Mr
  • 19
  • 5
0

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,
      });
    }
  }
}

TSVersion

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;
Omar Omeiri
  • 1,506
  • 1
  • 17
  • 33
-2

let bytes = 1024 * 10 * 10 * 10;

console.log(getReadableFileSizeString(bytes))

will return 1000.0Кб instead of 1MB

webolizzer
  • 337
  • 2
  • 5