
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
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 sinalEstas funções só conhecem o inteiro. Para arredondar a duas casas decimais, multiplica-se, arredonda-se, divide-se:
Math.round(1.345 * 100) / 100; // 1.35
Math.round(1.005 * 100) / 100; // 1 : esperava-se 1.01, ver abaixoA 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:
// 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
(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çãotoFixed 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()
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.
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.
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:
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
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.
import numeral from 'numeral';
numeral(50000).format('0,0'); // 50,000
numeral(23000).format('0a'); // 23k
numeral(2348895676).format('0.00b'); // 2.35GBMas 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
(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.(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.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.format() em cada valor.

