Convertendo o tamanho do arquivo em bytes em sequência legível por humanos

239

Estou usando esta função para converter um tamanho de arquivo em bytes para um tamanho de arquivo legível por humanos:

function getReadableFileSizeString(fileSizeInBytes) {
    var i = -1;
    var byteUnits = [' kB', ' MB', ' GB', ' TB', 'PB', 'EB', 'ZB', 'YB'];
    do {
        fileSizeInBytes = fileSizeInBytes / 1024;
        i++;
    } while (fileSizeInBytes > 1024);

    return Math.max(fileSizeInBytes, 0.1).toFixed(1) + byteUnits[i];
};

No entanto, parece que isso não é 100% exato. Por exemplo:

getReadableFileSizeString(1551859712); // output is "1.4 GB"

Isso não deveria ser "1.5 GB"? Parece que a divisão em 1024 está perdendo precisão. Estou totalmente entendendo mal alguma coisa ou existe uma maneira melhor de fazer isso?

Hristo
fonte
3
getReadableFileSizeString (0); retorna 0,1kb; p
Daniel Magnusson
2
Por que deveria ser 1,5? É o 1.445281982421875que arredonda corretamente para 1,4.
mpen
1
1551859712 / (1024 ^ 3) = 1.445281982421875 que está correto!
HM
2
Eu amo que você adicionou YB. Duvidoso, alguém receberá 1 YB por seu DB. Vai custar 100 trilhões de dólares !
guyarad
4
@guyarad - há uma imagem famosa de um disco rígido de 5 MB de 50 anos atrás (tinha o tamanho de uma sala e pesava cerca de uma tonelada). eu tenho certeza que naquela época eles nem sequer sonhar com GB e TB, e olhar para onde estamos hoje ... nunca diga nunca ;-)
TheCuBeMan

Respostas:

45

Depende se você deseja usar a convenção binária ou decimal.

A RAM, por exemplo, é sempre medida em binário, de modo que expressar 1551859712 como ~ 1,4GiB estaria correto.

Por outro lado, os fabricantes de disco rígido gostam de usar decimal, então chamariam ~ 1.6GB.

E, para ser confuso, os disquetes usam uma mistura dos dois sistemas - seus 1 MB são na verdade 1024000 bytes.

Neil
fonte
3
ceia engraçada ;-) "só para confundir, os disquetes usam uma mistura dos dois sistemas - seus 1 MB são na verdade 1024000 bytes."
FranXho
verdadeiros, tamanhos RAM são medidos utilizando unidades IEC, disco tamanhos usando métrica .. há um módulo npm isomorphic para converter ambos: byte-size
Lloyd
351

Aqui está um que eu escrevi:

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(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
fonte
1
Estou fazendo um ajuste: ao avaliar o limite, pegue o valor absoluto. Dessa forma, a função suportará valores negativos. Boa função! Obrigado por não usar uma declaração de opção !!
Aaron Blenkush
20
@AaronBlenkush: Quando você teria um tamanho de arquivo negativo?
MPEN
14
Acabei de copiar sua função para uma planilha do Google que estou usando para mostrar o tamanho delta após uma operação de "limpeza". Antes, Depois e Dif. A operação de limpeza resultou no crescimento de algumas tabelas do banco de dados e na redução em outras. Por exemplo, a Tabela A tem uma diferença de -1,95 MB, enquanto a Tabela B tem uma diferença de 500 kB. Portanto: positiva e negativa :-)
Aaron Blenkush
Aqui está a versão compactada do script:function humanFileSize(B,i){var e=i?1e3:1024;if(Math.abs(B)<e)return B+" B";var a=i?["kB","MB","GB","TB","PB","EB","ZB","YB"]:["KiB","MiB","GiB","TiB","PiB","EiB","ZiB","YiB"],t=-1;do B/=e,++t;while(Math.abs(B)>=e&&t<a.length-1);return B.toFixed(1)+" "+a[t]}
RAnders00
1
@ RAnders00: Obrigado pela versão reduzida. Pode dizer-me, porém, por que você inseriu os dois personagens invisíveis Unicode U + 200C (ZERO LARGURA NÃO JOINER) e U + 200B (ZERO LARGURA espaço) depois da E oft EiB ? Isso pretende ser uma marca d'água, para que você possa rastrear quem usou esse código? Nesse caso, acho que você deveria ter feito isso transparente em sua postagem.
Leviathan
81

Outra modalidade do cálculo

function humanFileSize(size) {
    var i = Math.floor( Math.log(size) / Math.log(1024) );
    return ( size / Math.pow(1024, i) ).toFixed(2) * 1 + ' ' + ['B', 'kB', 'MB', 'GB', 'TB'][i];
};
Andrew V.
fonte
8
parece que não lida com 0
Offirmo
4
Ele lida ou não com 0? Afinal, isso com um if (size == 0) {} else {} ainda é mais elegante do que eu já vi.
Rodrigo
13
Alterar a primeira linha para var i = size == 0 ? 0 : Math.floor( Math.log(size) / Math.log(1024) );parece fazer o truque se for 0. Ele retornará "0 B".
Gavin
Apenas para sua informação; Eu sei que a resposta é JavaScript simples, mas se alguém não quiser usá-lo no TypeScript, ele não funcionará (não digitado corretamente, como você está fazendo toFixede depois fazendo matemática com uma string. O que * 1faz?
Frexuz
1
A *1altera o tipo de dados de string para número, portanto, para o valor 1024que você começa 1 kB, em vez de 1.00 kB. Você pode fazer feliz o TypeScript fazendo Number((size / Math.pow(1024, i)).toFixed(2))o mesmo.
Adrian T
38

Aqui está um protótipo para converter um número em uma sequência legível, respeitando os novos padrões internacionais.

Há duas maneiras de representar grandes números: você pode exibi-los em múltiplos de 1000 = 10 3 (base 10) ou 1024 = 2 10 (base 2). Se você dividir por 1000, provavelmente usará os nomes de prefixo SI, se dividir por 1024, provavelmente usará os nomes de prefixo IEC. O problema começa com a divisão por 1024. Muitos aplicativos usam os nomes de prefixo SI e alguns usam os nomes de prefixo IEC. A situação atual está uma bagunça. Se você vir nomes de prefixos de SI, não saberá se o número é dividido por 1000 ou 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});

Essa função não contém loope, portanto, é provavelmente mais rápida do que algumas outras funções.

Uso:

Prefixo IEC

console.log((186457865).fileSize()); // default IEC (power 1024)
//177.82 MiB
//KiB,MiB,GiB,TiB,PiB,EiB,ZiB,YiB

Prefixo SI

console.log((186457865).fileSize(1)); //1,true for SI (power 1000)
//186.46 MB 
//kB,MB,GB,TB,PB,EB,ZB,YB

Defino o IEC como padrão, porque sempre usei o modo binário para calcular o tamanho de um arquivo ... usando o poder de 1024


Se você quer apenas um deles em uma função curta oneliner:

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

Uso:

console.log(fileSizeIEC(7412834521));

Se você tiver alguma dúvida sobre as funções, basta perguntar

cocco
fonte
código compacto muito bom, eu pessoalmente adicionaria alguns caracteres extras para controlar as casas decimais.
Orwellophile
Oi! Na verdade, o código é como eu escrevi pela primeira vez no jsfiddle. Nos últimos anos, aprendi a usar taquigrafia e bit a bit. Dispositivos móveis lentos, internet lenta, pouco espaço ... economizando muito tempo. Mas isso não é tudo, o desempenho geral aumentou drasticamente em todos os navegadores e todo o código é carregado muito mais rápido ... eu não uso o jquery, portanto não preciso carregar 100kb todas as vezes. Também preciso dizer que escrevo javascript também em microcontroladores, Smart TVs, consoles de jogos. aqueles tem espaço limitado (MCU), o desempenho connnection (de SmartTV) e, naturalmente, às vezes lento (Mobile)
Cocco
Disse que espero que você entenda minha escolha. Tudo o que posso fazer é explicar o que você não entende ou, do outro lado, sempre fico feliz em aprender coisas novas. Se houver algo no meu código que possa aumentar o desempenho ou economizar espaço, fico feliz em ouvi-lo.
Cocco
18
A minificação deve fazer parte do seu processo de criação, não do seu estilo de codificação. Nenhum desenvolvedor sério usará esse código por causa disso, pois leva muito tempo para ler e verificar a exatidão.
Huysentruitw
1
Para aqueles que odeiam ver "15.00 Bytes", você pode apenas modificar um pouco esta parte:.toFixed(e ? 2 : 0)
Lukman 6/17
20
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
fonte
2
Se você queria se livrar do espaço extra para bytes, você poderia usar o espaço com largura de zero \u200b: '\u200bKMGTP'.
Cdmckay
15

Solução como componente ReactJS

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

ATUALIZAÇÃO Para aqueles que usam es6, aqui está uma versão sem estado desse mesmo componente

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,
};
Patrick Mencias-lewis
fonte
1
Ótimo, obrigado. Você só esqueceu "bytes" dentro Math.log () na primeira linha de getBytes funcionar
BaptWaels
Muito agradável. Para desambiguação, e com a notação ES6, você pode usar isto: return (! Bytes && '0 Bytes') || ${(bytes / (1024 ** i)).toFixed(2)} ${suffixes[i]};
Little Brain
12

Com base na idéia de cocco , aqui está um exemplo menos compacto, mas espero mais abrangente.

<!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> 
KitKat
fonte
8

Eu queria o comportamento "gerenciador de arquivos" (por exemplo, Windows Explorer) em que o número de casas decimais é proporcional ao tamanho do número. Aparentemente, nenhuma das outras respostas faz isso.

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

Aqui estão alguns exemplos:

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
fonte
o uso de toFixed o converte em uma string, para que sua rodada seja uma string ou um número. Como é uma prática ruim, é possível convertê-lo facilmente em um número: #+num.tofixed(2)
Vincent Duprez
Não .toPrecision(3)cobre todos esses casos? Ah ... acho que não cobre entre 1000 e 1023. Que chatice.
mpen
7

Outro exemplo semelhante aos aqui

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';
}

Ele mede um desempenho insignificante melhor que os outros com recursos semelhantes.

Nick Kuznia
fonte
Isso fornece melhor desempenho do que algumas outras respostas. Eu estou usando isso. Alguns outros fizeram minhas abas do Chrome travarem e consumirem 99,9% da CPU enquanto eu fazia um cálculo periódico.
Nir Lanka
5

Aqui está o meu - também funciona para arquivos muito grandes -_-

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;
}
difícil
fonte
Ele combina o desempenho do loop e o uso da exponenciação, além de ser bastante difícil de ler. Eu realmente não entendo o ponto.
espectros
2
Não use então. É isso é cpu apenas clientside usados então quem se importa;)
fiffy
2
@fiffy Bem, a CPU do cliente também é preciosa, especialmente em dispositivos móveis e com aplicativos complexos. :)
Raito 06/07
5

Com base na resposta de cocco, mas um pouco desugerificada (honestamente, aquelas com as quais eu me senti confortável são mantidas / adicionadas) e não mostra zeros à direita, mas ainda suporta 0, espero ser útil para outras pessoas:

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>'));

Ebrahim Byagowi
fonte
4
1551859712 / 1024 = 1515488
1515488 / 1024 = 1479.96875
1479.96875 / 1024 = 1.44528198242188

Sua solução está correta. O importante a ser percebido é que, para passar de 1551859712para 1.5, é necessário fazer divisões em 1000, mas os bytes são contados em blocos binário-decimais de 1024, daí o valor do Gigabyte ser menor.

Eli
fonte
@ Eli ... sim, parece que sim. Acho que esperava "1.5" desde o seu 1551859712, mas isso significaria que estou em decimal e não em binário.
Hristo
3

Achei a resposta do @cocco interessante, mas tive os seguintes problemas:

  1. Não modifique tipos nativos ou tipos que você não possui
  2. Escreva um código limpo e legível para humanos, deixe que os minimizadores otimizem o código para máquinas
  3. (Bônus para usuários TypeScript) Não funciona bem com 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
fonte
1

Esta é a melhoria de tamanho da resposta mpen

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`;    
}

Kamil Kiełczewski
fonte
0

Para quem usa Angular, existe um pacote chamado angular-pipesque possui um canal para isso:

Arquivo

import { BytesPipe } from 'angular-pipes';

Uso

{{ 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 para os documentos .

Sinandro
fonte
0

Minha resposta pode estar atrasada, mas acho que ajudará alguém.

Prefixo métrico:

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

Prefixo binário:

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

Exemplos:

// 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
Kerkouch
fonte
-1

deixe bytes = 1024 * 10 * 10 * 10;

console.log (getReadableFileSizeString (bytes))

retornará 1000,0Кб em vez de 1MB

webolizzer
fonte