Pular para o conteúdo
Cursos

DevClub

LógicaFront-endBack-endMobile

IA Club

IA na prática
Estudar programaçãoEstudar IA
LiçãoIniciantecódigo testado

Converter string em número em JavaScript

Number, parseInt, parseFloat e o unário mais: o que cada um devolve, o radix esquecido e como o NaN silencioso vira um "R$ NaN" na tela do cliente.

Rodolfo Mori6 min de leitura

Valores vindos de <input>, parâmetros de URL e arquivos CSV normalmente chegam como strings, mesmo quando mostram dígitos. Conversão numérica é a operação que transforma esse texto em um valor do tipo number antes de calcular. O JavaScript oferece funções parecidas, mas elas não validam a entrada da mesma forma.

Imagine uma etiqueta entregue ao caixa. Number só aceita a etiqueta inteira como um número válido; parseFloat começa pela esquerda e para ao encontrar um caractere que não participa da leitura; parseInt faz essa leitura para inteiro. No código, isso explica por que '12abc' vira NaN com Number e 12 com os parsers. Escolher um deles também é escolher sua regra de validação.

Use Number quando todo o texto precisa ser válido. Use parseFloat apenas quando aproveitar um prefixo é realmente desejado, parseInt para inteiro com radix explícito e trate o + unário como a forma curta — e menos legível — de Number.

js
const digitado = '289.90';

console.log(Number(digitado));
console.log(parseInt(digitado, 10));
console.log(parseFloat(digitado));
console.log(+digitado);
289.9 289 289.9 289.9

Repare que parseInt já cortou os centavos. Com uma entrada bem comportada, três das quatro concordam. O interessante começa quando a entrada não é bem comportada — que é sempre.

Number é rígida, e transforma vazio em zero

js
console.log(Number('12abc'));
console.log(Number(''));
console.log(Number('   '));
console.log(Number(null));
console.log(Number(undefined));
console.log(Number(true));
NaN 0 0 0 NaN 1

Duas informações valem ouro aí.

A primeira: Number('12abc') é NaN. Ou a string inteira é um número, ou não é número nenhum. Não existe meio-termo, e é isso que torna Number a escolha certa para validar entrada.

A segunda, e essa derruba gente todo mês: Number('') é zero. Campo de formulário em branco não vira NaN, vira 0. Se a sua validação é if (Number.isNaN(valor)), o campo vazio passa direto e o pedido é criado com quantidade zero. Number(null) também é 0, pelo mesmo motivo; só Number(undefined) devolve NaN.

parseInt lê até o primeiro caractere inválido

parseInt tem outra filosofia: ele varre a string da esquerda para a direita e para no primeiro caractere que não serve, devolvendo o que já leu.

js
console.log(parseInt('12abc', 10));
console.log(parseInt('  42 unidades', 10));
console.log(parseInt('abc12', 10));
console.log(parseInt('', 10));
console.log(parseInt('289.90', 10));
12 42 NaN NaN 289

Ele pula espaço em branco no começo, aceita lixo no fim e desiste quando o lixo vem primeiro. A última linha é a que mais custa caro: o ponto decimal não é um caractere válido para inteiro, então parseInt('289.90') devolve 289 e joga os noventa centavos fora, sem avisar.

Note também que parseInt('') é NaN, enquanto Number('') é 0. Duas funções da mesma linguagem discordando sobre o significado de string vazia.

O radix, e o mapa que devolve NaN no meio

O segundo argumento do parseInt é a base numérica. Ele é opcional, e essa opcionalidade já foi um dos bugs mais famosos do JavaScript:

js
console.log(parseInt('08'));
console.log(parseInt('08', 10));
console.log(parseInt('0x1f'));
console.log(parseInt('11', 2));
console.log(['1', '7', '11'].map(parseInt));
8 8 31 3 [ 1, NaN, 3 ]

Antes do ES5, uma string começada em 0 era interpretada como octal — e parseInt('08') devolvia 0, porque 8 não existe na base 8. Um campo de mês ou de dia com zero à esquerda virava zero. Hoje esse comportamento acabou: as duas primeiras linhas dão 8.

O prefixo 0x, porém, continua valendo: parseInt('0x1f') lê hexadecimal e devolve 31. E a base explícita funciona — parseInt('11', 2) lê binário e devolve 3.

A última linha é o motivo real de sempre passar o radix. map chama a função com três argumentos: valor, índice e array. O índice vai parar no lugar do radix. Então a conversão foi parseInt('1', 0), parseInt('7', 1) e parseInt('11', 2) — base 1 não existe, daí o NaN no meio. Quem escreve .map(Number) não tem esse problema, porque Number ignora os argumentos extras.

parseFloat e a vírgula brasileira

parseFloat é o parseInt com casas decimais: mesma tolerância, mesmo “leia até onde der”. E aqui aparece o problema que ninguém escapa no Brasil:

js
console.log(parseFloat('289,90'));
console.log(Number('289,90'));
console.log(parseFloat('1.234,56'));
console.log(Number('1.234,56'));
289 NaN 1.234 NaN

Nenhuma das duas entende vírgula decimal. O parseFloat para na vírgula e devolve 289 — perdendo os centavos em silêncio. Pior: em '1.234,56' ele lê o ponto como separador decimal e devolve 1.234, transformando mil duzentos e trinta e quatro reais em um real e vinte e três centavos. O Number pelo menos falha alto, devolvendo NaN.

Não existe função nativa que leia o formato brasileiro. Você precisa normalizar o texto antes — é o que a última seção resolve.

O unário + é o Number disfarçado

Colocar um + na frente de um valor força a conversão numérica, com exatamente as mesmas regras do Number:

js
console.log(+'289.90');
console.log(+'');
console.log(+[]);
console.log(+['7']);
console.log(+{});
console.log(+'1e3');
289.9 0 0 7 NaN 1000

As linhas com colchete e chave explicam por que ele é controverso. Array vazio vira 0, array de um elemento vira aquele elemento convertido, objeto vira NaN — tudo consequência da conversão para string antes da conversão para número.

É conciso e é idiomático em código de biblioteca. Em código de time, Number(x) diz o que faz sem ninguém precisar saber essas regras.

Erros comuns

O erro característico deste assunto não é uma exceção: é o NaN que ninguém percebeu.

js
const formulario = { quantidade: '2', precoUnitario: 'R$ 289,90' };

const total = Number(formulario.precoUnitario) * Number(formulario.quantidade);

console.log(`Total: R$ ${total.toFixed(2)}`);
Total: R$ NaN

Nenhum erro foi lançado. toFixed funcionou normalmente — NaN é number, e number tem toFixed. O resultado foi para a tela do cliente escrito por extenso: R$ NaN. Todo desenvolvedor front-end já viu isso em produção pelo menos uma vez.

Quando ele finalmente derruba o programa, é longe da causa:

js
const faixasDeFrete = [12.5, 18.9, 27.4];

const peso = Number('2,5');
const faixa = faixasDeFrete[Math.floor(peso)];

console.log(faixa.toFixed(2));
file:///private/tmp/loja/l7-propagacao.mjs:6 console.log(faixa.toFixed(2)); ^

TypeError: Cannot read properties of undefined (reading ‘toFixed’) at file:///private/tmp/loja/l7-propagacao.mjs:6:19 Node.js v24.16.0

Siga a cadeia de trás para frente: faixa é undefined porque o índice era NaN; o índice era NaN porque Math.floor(NaN) é NaN; e o peso era NaN porque '2,5' tem vírgula. O erro aponta a linha 6, e o defeito está na linha 3.

É a mesma leitura descrita em Cannot read properties of undefined: a mensagem indica onde estourou, não onde nasceu. Quando um NaN aparecer, procure a primeira conversão da cadeia.

Vale ver as quatro funções discordando lado a lado:

js
const entradas = ['289.90', '289,90', 'R$ 1.234,56', '', '  ', '12abc'];

for (const entrada of entradas) {
  console.log(
    JSON.stringify(entrada).padEnd(14),
    'Number=' + String(Number(entrada)).padEnd(8),
    'parseFloat=' + String(parseFloat(entrada)).padEnd(8),
    'parseInt=' + parseInt(entrada, 10),
  );
}
"289.90" Number=289.9 parseFloat=289.9 parseInt=289 "289,90" Number=NaN parseFloat=289 parseInt=289 "R$ 1.234,56" Number=NaN parseFloat=NaN parseInt=NaN "" Number=0 parseFloat=NaN parseInt=NaN " " Number=0 parseFloat=NaN parseInt=NaN "12abc" Number=NaN parseFloat=12 parseInt=12

Seis entradas, e em quatro delas as funções discordam. Escolher pela conveniência do momento é como o mesmo formulário passa a aceitar valores diferentes em telas diferentes.

Um conversor seguro para formulário

A regra é converter na borda — no instante em que o dado entra no sistema — e devolver null em vez de NaN, porque null não sobrevive a uma conta sem alguém perceber:

js
function paraNumero(texto) {
  const limpo = String(texto)
    .replace(/[^\d,.-]/g, '')
    .replace(/\./g, '')
    .replace(',', '.');

  if (limpo === '') return null;

  const numero = Number(limpo);
  return Number.isNaN(numero) ? null : numero;
}

console.log(paraNumero('R$ 1.234,56'));
console.log(paraNumero('289,90'));
console.log(paraNumero('grátis'));
console.log(paraNumero(''));
1234.56 289.9 null null

Três passos, na ordem: remove tudo que não é dígito, vírgula, ponto ou sinal; apaga os pontos de milhar; troca a vírgula decimal por ponto. O if antes do Number existe justamente por causa do Number('') === 0 que vimos lá em cima — sem ele, 'grátis' viraria zero.

Ele é deliberadamente brasileiro, e isso tem preço: como apaga todo ponto, paraNumero('289.90') no formato americano devolve 28990. Decida qual formato o campo aceita antes de converter — um conversor que tenta adivinhar os dois é como o erro de cem vezes o valor entra no pedido.

Para o caminho inverso, exibir o número no formato brasileiro, o próprio JavaScript resolve:

js
const total = 1234.56;

console.log(total.toFixed(2));
console.log(total.toLocaleString('pt-BR', { style: 'currency', currency: 'BRL' }));
1234.56 R$ 1.234,56

Converter na entrada, calcular sempre com número, formatar só na saída. Esses três hábitos eliminam o R$ NaN do seu projeto de uma vez.

Teste o conversor com uma tabela de casos: '0', '', ' ', '12abc', '289,90' e 'R$ 1.234,56'. Para cada entrada, escreva antes o resultado esperado e depois compare com a função. A prática termina quando vazio e texto inválido viram null, zero continua sendo 0 e o preço brasileiro vira 1234.56.

Com isso o bloco de fundamentos da trilha de JavaScript está fechado: você já sabe rodar código, imprimir, guardar valores, reconhecer tipos e converter entre eles. O próximo salto são funções, onde essa lógica passa a ter nome e a ser reaproveitada — e o guia de JavaScript mostra o que vem depois delas.

  • javascript
  • conversao
  • number
  • parseint
  • nan

Perguntas frequentes

Qual a diferença entre Number e parseInt?
Number exige que a string inteira seja um número válido e devolve NaN se sobrar qualquer caractere. parseInt lê da esquerda até encontrar algo que não serve e devolve o que conseguiu, descartando o resto em silêncio.
Ainda preciso passar o radix no parseInt?
Desde o ES5 a base 10 é o padrão para string começada em zero, então parseInt("08") já devolve 8. Passar 10 continua valendo por dois motivos: o prefixo 0x ainda muda a base, e todo linter cobra o argumento.
Como converter um preço com vírgula, tipo "1.234,56"?
Nenhuma função nativa entende o formato brasileiro. É preciso remover o ponto de milhar e trocar a vírgula por ponto antes de chamar Number — ou usar Intl.NumberFormat no sentido inverso, para exibir.
Por que Number("") devolve 0 e não NaN?
A especificação define que string vazia, ou só com espaços, converte para zero. É consistente com Number(null) e Number(false), e é a causa de campo de formulário em branco virar zero em vez de virar erro de validação.

Dúvidas e comentários

Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.

Todo o código deste artigo foi executado em Node 24.16.0, e as saídas exibidas são as reais — como produzimos este conteúdo.

Fontes consultadas

  1. MDN — Number() — developer.mozilla.org
  2. MDN — parseInt() — developer.mozilla.org
  3. ECMAScript — ToNumber Applied to the String Type — tc39.es

Continue por aqui