Um único tipo numérico para (quase) tudo
A maioria das linguagens oferece tipos separados para inteiros e números de ponto flutuante. O JavaScript, historicamente, só oferece um: Number. Não importa se você escreve 42, 3.14 ou -0.001 — você sempre recebe o mesmo primitivo, um ponto flutuante de precisão dupla de 64 bits no padrão IEEE 754.
Bem prático — sem precisar ficar convertendo entre int e float, nem se preocupar com estouro em 2^31. Só que essa representação em ponto flutuante tem suas consequências, e elas pegam os iniciantes de jeito o tempo todo. Por isso, em 2020 foi adicionado um segundo tipo numérico, o BigInt, justamente para cobrir os casos em que o Number não dá conta.
A pegadinha do ponto flutuante
Rode este código:
A primeira linha imprime 0.30000000000000004. A segunda imprime false. Isso não é uma esquisitice do JavaScript — Python, Java, C e qualquer linguagem que use ponto flutuante IEEE 754 se comporta da mesma forma.
O motivo: 0.1 e 0.2 não têm representação exata em binário, assim como 1/3 não pode ser escrito de forma exata em decimal. O que fica armazenado é a aproximação binária mais próxima, e pequenos erros vão se acumulando. O modelo mental ideal é: trate valores Number com casas decimais como aproximações que, por acaso, chegam bem perto do que você escreveu.
Quando for lidar com dinheiro, não guarde $19,99 como 19.99. Armazene os centavos como inteiros — 1999 — e só formate na hora de exibir. Esse é, disparado, o melhor hábito para fugir de bugs de precisão com ponto flutuante.
Comparando floats com segurança
Como a igualdade direta não é confiável, compare usando uma tolerância sempre que precisar:
Number.EPSILON representa a menor diferença entre 1 e o próximo número representável — serve bem como tolerância padrão para valores próximos de 1. Já para magnitudes muito grandes ou muito pequenas, o ideal é usar uma tolerância que acompanhe a escala dos valores.
O intervalo de inteiros seguros
Até um certo tamanho, os inteiros são representados de forma exata em um float de 64 bits. A partir desse limite, você começa a perder precisão bit a bit:
2^53 - 1 é o último inteiro até o qual todos os inteiros abaixo dele conseguem ser representados. A partir daí, alguns inteiros simplesmente não existem no tipo Number — eles acabam arredondados para o vizinho mais próximo. É uma corrupção silenciosa de dados esperando para acontecer, principalmente se você está lendo IDs de 64 bits de um banco como números JSON comuns.
Conheça o BigInt
O BigInt é um tipo primitivo separado, feito para inteiros de precisão arbitrária. Para criar um, basta adicionar n no final de um literal inteiro, ou chamar BigInt(...):
BigInts não têm limite superior além da memória disponível. Eles são a ferramenta certa para:
- IDs de banco de dados ou snowflake IDs do Twitter/X que ultrapassam
2^53. - Matemática criptográfica.
- Qualquer aritmética com inteiros onde o resultado exato importa mais do que a velocidade bruta.
Eles não são a escolha certa para contadores do dia a dia, índices de array ou dinheiro representado em centavos — o Number tradicional é mais rápido e conversa com todas as APIs da linguagem.
Aritmética com BigInt
Todos os operadores usuais funcionam, desde que os dois operandos sejam BigInts:
A divisão trunca em direção ao zero — não existe BigInt fracionário. Se você precisa de casa decimal, volta pro território do Number (ou parte pra uma biblioteca de decimais).
Não misture os tipos
A regra que pega todo mundo de surpresa: você não pode misturar Number e BigInt na mesma expressão.
Comparação é a única exceção à regra — <, >, == fazem coerção entre os dois tipos:
Ou seja, o == trata os dois como iguais, mas o === não. Se você já adotou === em todo lugar (e deveria mesmo), encare comparações numéricas entre esses dois tipos como um cheiro de projeto — escolha um lado e faça a conversão.
Convertendo entre Number e BigInt
São duas conversões possíveis, cada uma com sua pegadinha:
Ir de Number → BigInt é rigoroso: decimais e NaN lançam erro. Já o caminho BigInt → Number é permissivo, porém com perda — qualquer valor acima de MAX_SAFE_INTEGER é arredondado. Se você está convertendo um BigInt que veio de um servidor, pare e pense se realmente precisa dessa conversão.
Valores especiais do tipo Number
Aproveitando o assunto, existem três valores dentro do tipo Number que, matematicamente falando, não são números:
Infinity e -Infinity aparecem quando você divide por zero ou estoura o limite de ponto flutuante. Já NaN ("not a number", ou "não é um número") surge quando uma operação aritmética não produz um resultado válido.
Um detalhe curioso: NaN não é igual a si mesmo — isso faz parte da especificação IEEE 754, não é um bug do JS. Para checar, use Number.isNaN(x). A função global isNaN, mais antiga, faz coerção do argumento antes de comparar, o que gera respostas erradas (isNaN("hello") retorna true). Prefira sempre Number.isNaN.
Convertendo strings em números
Entradas de usuário e números vindos de JSON costumam chegar como string. Há três formas comuns de fazer a conversão:
Number() é rigoroso — qualquer coisa não numérica vira NaN, com exceção da string vazia e espaços em branco, que resultam em 0. Já parseInt e parseFloat são mais tolerantes: leem o que conseguem e param quando esbarram em algo inválido. Escolha a opção que combina com a sua intenção e sempre confira se o resultado é NaN antes de usar.
Para converter strings em BigInt, use BigInt("123") — é rigoroso e lança erro se a entrada for inválida.
Um guia rápido de bolso
- Para contadores, contas, coordenadas e a maioria dos números do dia a dia: use
Number. - Para dinheiro: trabalhe em centavos inteiros com
Number, ou recorra a uma biblioteca decimal. - Para inteiros maiores que
2^53(IDs de banco de dados, criptografia, combinatória): useBigIntcom o sufixon. - Compare números de ponto flutuante com uma tolerância, nunca com
===. - Verifique resultados inválidos com
Number.isNaNeNumber.isFinite, não com as versões globais. - Não misture
NumbereBigIntna mesma expressão — faça a conversão explícita.
A seguir: null vs undefined
O JavaScript tem duas formas de dizer "sem valor" — null e undefined — e elas não são intercambiáveis. No próximo tópico: o que cada uma significa, em que diferem e quando usar cada uma.
Perguntas frequentes
Por que 0.1 + 0.2 não dá 0.3 em JavaScript?
Porque o tipo Number do JavaScript é um float de 64 bits no padrão IEEE 754, e nem 0.1 nem 0.2 têm representação exata em binário. O resultado acaba sendo 0.30000000000000004. Não é bug do JavaScript — acontece igualzinho em Python, Java e qualquer linguagem que use esse mesmo formato de ponto flutuante. Para trabalhar com dinheiro, a saída é trabalhar com inteiros (centavos) ou usar uma biblioteca de decimais.
O que é BigInt no JavaScript e quando devo usar?
BigInt é um tipo numérico primitivo separado, feito para inteiros maiores que Number.MAX_SAFE_INTEGER (2^53 - 1). Você cria um adicionando n no final — 9007199254740993n — ou chamando BigInt(valor). Use quando precisar lidar com IDs de 64 bits vindos do banco, criptografia, ou qualquer conta com inteiros em que precisão importa mais do que velocidade.
Dá para misturar Number e BigInt em JavaScript?
Não dá. 1n + 1 dispara TypeError: Cannot mix BigInt and other types. Você precisa converter explicitamente com BigInt(n) ou Number(b). Operadores de comparação como < e == funcionam entre os dois tipos, mas === sempre retorna false porque os tipos são diferentes.