O que acontece quando o computador não consegue somar dois números direito
Você já passou por isso: soma duas coisas simples no JavaScript — algo como 0.1 mais 0.2 — e aparece 0.30000000000000004 na tela. Parece bug, mas não é. É o padrão IEEE 754 sendo o que ele sempre foi: uma aproximação, não uma verdade absoluta. E essa divergência aparece em qualquer linguagem que use ponto flutuante binário, que é praticamente todas.
Entendendo o problema com número decimal
O problema com número decimal existe porque os computadores guardam números em base 2, não em base 10. Números como 0.1 e 0.2 são infi nitos quando convertidos para binário — o mesmo jeito que 1/3 é 0,333... em base 10. O computador corta na hora. E nesse corte que sobra. O IEEE 754 define três formatos principais de precisão: float de 32 bits, double de 64 bits e long double de 80 ou 128 bits, dependendo da plataforma. O double é o padrão na maioria das linguagens modernas. Ele dá cerca de 15 dígitos decimais de precisão antes que o erro comece a aparecer de forma consistente. Para a maioria dos cálculos técnicos isso é mais que suficiente. Para dinheiro, não é.
Quando eu estava ajustando um sistema de cálculo de impostos internos na empresa, o módulo de notas fiscais gerava diferenças de centavos que só apareciam depois de processar milhares de linhas. Não era um erro óbvio — era preciso rodar um script de comparação linha a linha com tolerance menor que 0,001. O problema estava no acúmulo: cada operação intermediária de divisão e multiplicação adicionava um erro de arredondamento invisível. A solução foi trocar o parseFloat por uma biblioteca que trabalha com escalares inteiros. Converti todos os valores centrais para reais inteiros (multiplicando por 100), fiz as contas nessa base e só no final convertia de volta. A parte contraintuitiva é que usar BigDecimal ou Decimal não resolve automaticamente. Você precisa garantir que a conversão de string para decimal aconteça no início do pipeline, não no meio. Se um valor já tiver passado por uma operação em ponto flutuante antes de chegar ao seu type conversor, o dano já está feito. O erro entra cedo e você só vê tarde.
Como contornar isso na prática
A estratégia mais segura depende do que você está construindo. Vou listar as opções do mais simples ao mais robusto, com os pontos cegos de cada uma. Arredondamento manual com Math.round(). Simples, funciona para exibir resultados, mas não resolve o erro nas contas intermediárias. Use apenas para formatação de saída. Se você arredondar só no final após uma sequência de operações, o resultado já veio errado por causa do acúmulo anterior.
Bibliotecas de decimal. No JavaScript, bibliotecas como decimal.js ou big.js manipulam números em base 10 internamente. Elas resolvem o problema para quase todos os casos práticos. A desvantagem é que não se integram com aritmética nativa — você não pode misturar Decimal com Number sem converter explicitamente. E a performance cai significativamente em laços grandes: comparado ao double nativo, operações com decimal.js levam cerca de 10 a 50 vezes mais tempo por operação. Trabalhar com inteiros. Essa é a abordagem que eu recomendo quando o domínio permite. Dinheiro? Tudo em centavos. Comprimento em milímetros? Tudo em unidades inteiras. Não há precisão para perder. A limitação aqui é clara: não funciona para grandezas que são inerentemente fracionárias, como porcentagens de juros compostos em múltiplos períodos, coordenadas geográficas ou qualquer simulação física que dependa de divisões sucessivas.
👉 Clique no botão abaixo para saber mais sobre o assunto!
Rounding tolerance em comparações. Quando for testar igualdade entre dois floats, nunca use ==. Sempre adote uma margem de erro, tipo Math.abs(a - b)
1e-9. O valor de 1e-9 é arbitrário — ajuste conforme a escala dos seus dados. Esse cuidado é especialmente importante em testes automatizados, onde um floating-point mismatch pode fazer três testes falharem sem nenhuma razão lógica aparente.
Onde o problema com número decimal é mais perigoso
Alguns cenários escondem o erro de forma elegante. O mais comum é comparação de chaves em tabelas hash ou lookup por valor exatamente igual. Um ID calculado numericamente pode variar entre execuções se uma operação anterior introduziu deriva, e aí seu banco de dados retorna zero linhas. Isso acontece com frequência em pipelines de ETL que misturam SQL com Python ou JavaScript. Outro ponto cego é serialização. Quando você converte um objeto com campos float para JSON, a representação textual pode variar entre ambientes. Node.js e Chrome usam o mesmo algoritmo de stringificação, mas Java e Càs vezes escolhem representações diferentes para o mesmo double. Isso quebra contratos de API de forma silenciosa, porque o valor numérico dentro do JSON é idêntico, mas a string que o representa não é.
Operações de redimensionamento em WebGL ou canvas também sofrem. Coordenadas de textura mapeadas com ponto flutuante podem produzir artefatos visuais microscópicos que se tornam óbvios em close-up. A solução habitual é snapping para grade inteira ou uso de texture coordinates normalizadas com precisão controlada.
Alternativas quando nada mais funciona
Se o seu cálculo envolve muitos passos intermediários e a tolerância a erro é extremamente baixa, considere três caminhos: usar uma linguagem com suporte nativo a tipos decimais, como Ccom decimal ou Python com Decimal do módulo decimal; mudar para computação simbólica via bibliotecas como SymPy, que mantém frações exatas até o momento da avaliação numérica; ou aceitar a imprecisão e projetar o sistema para lidar com ela de forma explícita, registrando tolerâncias em cada fronteira entre módulos. O último ponto é o mais negligenciado. A maioria dos desenvolvedores espera que o computador calcule certo. Quando o computador não calcula certo, o primeiro instinct é procurar um bug no código. Gasta-se horas caçando NullPointer quando na verdade o problema é que 0.1 + 0.2 não é exatamente 0.3 em ponto flutuante. Documentar essa tolerância desde o design evita esse tipo de dor de cabeça tardia.
Se quiser algo concreto para começar, a biblioteca decimal.js tem instalação via npm e funciona direto no browser. Para Python, o módulo decimal já vem instalado. Para C#, o tipo decimal é built-in. Escolha o que se encaixa no seu stack e trate números decimais como o que eles são: aproximações, não verdades absolutas.