Formatar números em JavaScript: toFixed e Intl.NumberFormat

Arredondar a duas casas decimais, mostrar 1 234 567,89 à francesa, uma moeda, uma percentagem ou «1,4 k»: os métodos nativos para formatar um número em JavaScript, as suas armadilhas e o que resta do Numeral.js.

Formatar números em JavaScript com a biblioteca Numeral
Resposta rápida

Para mostrar um número, usa Intl.NumberFormat ou o seu atalho toLocaleString(): (1234.5).toLocaleString('fr-FR', { minimumFractionDigits: 2 }) dá «1 234,50», e a opção style: 'currency' acrescenta a moeda. Para arredondar sem mostrar, Math.round(n * 100) / 100; toFixed(2) também arredonda mas devolve uma string.

Um número calcula-se em Number e mostra-se em string: «1 234,50 €», «25,6 %», «1,4 k visualizações». Entre os dois, é preciso arredondar, colocar um separador de milhares, escolher a vírgula ou o ponto. O JavaScript faz tudo isto sem biblioteca, graças a toFixed, toLocaleString e sobretudo Intl.NumberFormat. Todos os resultados abaixo foram executados em Node.js 22.

Arredondar um número

Math.round, Math.floor, Math.ceil, Math.trunc

javascript
Math.round(2.5);   // 3   : para o mais próximo, .5 sobe
Math.round(-2.5);  // -2  : para +Infinito, não «para longe de zero»
Math.floor(4.7);   // 4   : para baixo
Math.floor(-4.2);  // -5
Math.ceil(4.2);    // 5   : para cima
Math.trunc(-4.7);  // -4  : corta a parte decimal, seja qual for o sinal

Estas funções só conhecem o inteiro. Para arredondar a duas casas decimais, multiplica-se, arredonda-se, divide-se:

javascript
Math.round(1.345 * 100) / 100;   // 1.35
Math.round(1.005 * 100) / 100;   // 1    : esperava-se 1.01, ver abaixo

A armadilha das casas decimais em binário

1.005 não existe em binário: o número guardado vale 1.00499999… e arredonda, portanto, para 1.00. Não é um bug do JavaScript mas do formato IEEE 754, comum a todas as linguagens. Duas defesas:

javascript
// 1. Corrigir o erro de representação antes de arredondar
Math.round((1.005 + Number.EPSILON) * 100) / 100;   // 1.01

// 2. Nunca guardar decimais: os preços em cêntimos inteiros
const prixCentimes = 1005;
(prixCentimes / 100).toFixed(2);                    // '10.05'

A segunda é a regra para tudo o que toca em dinheiro: somar cêntimos inteiros nunca produz 0.30000000000000004.

toFixed(): arredondar para mostrar

javascript
(1234.5678).toFixed(2);  // '1234.57'
(2.5).toFixed(0);        // '3'
(1.005).toFixed(2);      // '1.00'  : a mesma armadilha binária que Math.round
(0.1 + 0.2).toFixed(2);  // '0.30'  : é precisamente para isto que serve

typeof (1.5).toFixed(2); // 'string'
(1.5).toFixed(2) + 1;    // '1.501' : concatenação, não adição

toFixed devolve uma string. É perfeito para mostrar, perigoso para calcular: converte com Number() se tiveres de reutilizar o valor. E o seu ponto decimal é sempre um ponto; para a vírgula francesa, passa por toLocaleString.

Separador de milhares e formato francês: toLocaleString()

javascript
const n = 1234567.891;

n.toLocaleString('fr-FR');   // '1 234 567,891'
n.toLocaleString('en-US');   // '1,234,567.891'
n.toLocaleString('de-DE');   // '1.234.567,891'

(1234.5).toLocaleString('fr-FR', { minimumFractionDigits: 2 });   // '1 234,50'
(3.14159).toLocaleString('fr-FR', { maximumFractionDigits: 2 });  // '3,14'

Um método, três convenções nacionais, zero linhas de código de formatação. Duas precisões:

  • O separador de milhares francês não é um espaço vulgar mas um espaço fino não separável (U+202F). Impede a quebra de «1 234» no fim da linha. Se tiveres de o retirar, filtra com /\s/g, não com ' '.
  • Sem argumento de língua, toLocaleString() usa a do navegador ou a do sistema. Para um resultado estável, indica sempre 'fr-FR'.

Intl.NumberFormat: moeda, percentagem, unidades

toLocaleString é um atalho para Intl.NumberFormat. O objeto completo cria-se uma vez e reutiliza-se, o que conta assim que se formata uma lista ou uma tabela.

formats.js
const euros = new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR' });
euros.format(1234.5);   // '1 234,50 €'

const dollars = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' });
dollars.format(1234.5); // '$1,234.50'

const pourcent = new Intl.NumberFormat('fr-FR', { style: 'percent', maximumFractionDigits: 1 });
pourcent.format(0.256); // '25,6 %'  : o valor é um rácio, 0.256 e não 25.6

const km = new Intl.NumberFormat('fr-FR', { style: 'unit', unit: 'kilometer' });
km.format(12.5);        // '12,5 km'

const go = new Intl.NumberFormat('fr-FR', { style: 'unit', unit: 'gigabyte', maximumFractionDigits: 2 });
go.format(2.35);        // '2,35 Go'

O formatador de moeda coloca o símbolo no sítio certo consoante a língua (depois do valor em francês, antes em inglês), escolhe o número de casas decimais da moeda e insere o espaço não separável. São outras tantas regras que deixam de ter de ser programadas.

Notação compacta: 1,4 k e 1 M

Mostrar «1 400 visualizações» como «1,4 k» ou «1 000 000» como «1 M» é uma necessidade corrente. A opção notation: 'compact' faz isso, na língua pretendida.

javascript
const compact = new Intl.NumberFormat('fr-FR', { notation: 'compact' });
compact.format(1400);       // '1,4 k'
compact.format(1000000);    // '1 M'

new Intl.NumberFormat('en-US', { notation: 'compact' }).format(23000);   // '23K'
new Intl.NumberFormat('en-US', { notation: 'compact', maximumFractionDigits: 3 }).format(1230974);   // '1.231M'

new Intl.NumberFormat('fr-FR', { notation: 'compact', compactDisplay: 'long' }).format(2500000);   // '2,5 millions'

Outras opções úteis:

javascript
new Intl.NumberFormat('fr-FR', { minimumIntegerDigits: 2 }).format(7);          // '07'
new Intl.NumberFormat('fr-FR', { useGrouping: false }).format(1234567);        // '1234567'
new Intl.NumberFormat('fr-FR', { signDisplay: 'always' }).format(12);          // '+12'
new Intl.NumberFormat('en-US', { notation: 'scientific' }).format(676565765722); // '6.766E11'
new Intl.NumberFormat('fr-FR', { maximumFractionDigits: 2, roundingMode: 'halfExpand' }).format(1.005); // '1,01'

A última linha merece uma palavra: Intl.NumberFormat arredonda a partir da representação decimal do número, não da sua aproximação binária. 1.005 dá mesmo «1,01», onde toFixed dava «1.00». É mais uma razão para formatar com Intl em vez de o fazer à mão.

Zeros à esquerda e outras bases

javascript
String(7).padStart(2, '0');   // '07'  : horas, minutos, números de fatura
String(7).padStart(3, '0');   // '007'

(255).toString(16);   // 'ff'        : hexadecimal, para uma cor CSS
(255).toString(2);    // '11111111'  : binário

(123.456).toPrecision(4);   // '123.5'     : número total de algarismos significativos
(1234.5678).toExponential(2); // '1.23e+3'

Dois comportamentos de toString() a conhecer: acima de 1021 e abaixo de 10−6, o JavaScript passa para notação científica ((1e21).toString() dá '1e+21', (0.0000001).toString() dá '1e-7'). toFixed ou Intl.NumberFormat com notation: 'standard' mantêm a escrita por extenso.

A operação inversa

Uma string formatada não se reconverte tal e qual: Number('1 234,50') vale NaN. É preciso retirar os espaços e substituir a vírgula antes de converter. O procedimento completo está em Converter uma string em número em JavaScript.

E o Numeral.js?

A primeira versão deste artigo apresentava o Numeral.js, uma biblioteca de formato compacto: numeral(50000).format('0,0') para «50,000», '0a' para «23k», '0.00b' para «2.35GB», '0o' para «23rd». Continua em muitos projetos e a sua sintaxe é agradável.

javascript
import numeral from 'numeral';

numeral(50000).format('0,0');       // 50,000
numeral(23000).format('0a');        // 23k
numeral(2348895676).format('0.00b'); // 2.35GB

Mas a sua última versão, a 2.0.6, é de março de 2017 e o projeto já não é mantido: as locales estão congeladas e o separador francês não é o espaço fino esperado. Para um projeto novo, Intl.NumberFormat cobre as mesmas necessidades (milhares, compacto, moeda, percentagem) sem dependência, com as convenções reais de cada língua e peso nulo no bundle. Mantém o Numeral onde ele já está; não voltes a acrescentá-lo.

Resumo

Necessidade Ferramenta Exemplo
Arredondar para calcular Math.round(n * 100) / 100 ou cêntimos inteiros 1.35
Arredondar para mostrar, ponto decimal toFixed(2) '1234.57'
Formato francês, milhares e vírgula toLocaleString('fr-FR') '1 234 567,891'
Moeda, percentagem, unidade Intl.NumberFormat + style '1 234,50 €'
1,4 k / 1 M notation: 'compact' '1,4 k'
Zeros à esquerda padStart(2, '0') '07'

Erros frequentes

Somar o resultado de toFixed (1.5).toFixed(2) + 1 dá «1.501»: toFixed devolve uma string, a adição concatena. Converte com Number() se tiveres de voltar a calcular.
Esperar um arredondamento «da escola» (1.005).toFixed(2) vale «1.00» porque 1.005 é guardado como 1.00499999… em binário. Para um arredondamento financeiro, trabalha em cêntimos inteiros.
Substituir o espaço de toLocaleString('fr-FR') por um espaço normal O separador de milhares francês é um espaço fino não separável (U+202F). Um replace(' ', '') não o retira; usa /\s/g.
Recriar Intl.NumberFormat dentro de um ciclo A construção do formatador é dispendiosa. Cria-o uma vez e depois chama format() em cada valor.

JavaScript

Damien Flandrin Programador web desde 2010, criador da Gekkode e do Email Impact. Cada artigo é testado num projeto real antes de ser publicado. Contacto
Newsletter

Os novos testes, tutoriais e projetos, por e-mail.

Testes reproduzíveis, código versionado, resultados datados. Nunca spam.