Operadores aritméticos em JavaScript: do + ao **
Soma, subtração, resto com negativos, exponenciação e o clássico 0.1 + 0.2 — com a saída real de cada conta no Node e o bug silencioso do + com string.
JavaScript tem sete operadores aritméticos: +, -, *, /, %, ** e o par
++/--. Cinco deles se comportam como você aprendeu na escola. O + não — ele
é o único que muda de função quando um dos lados é texto, e essa exceção sozinha
responde por uma boa parte dos valores errados que aparecem num checkout.
A regra curta: -, *, /, % e ** convertem os dois lados para número
antes de calcular; o + só soma se nenhum dos lados for string. Se um lado for
string, ele concatena e devolve texto, sem erro nenhum.
A calculadora que troca de função ao encontrar texto
Numa calculadora comum, o sinal de mais sempre soma. No JavaScript ele trabalha
também como fita adesiva: se encontra texto em um dos lados, junta as duas
partes. Assim, o preço 149.9 e a quantidade '2' podem virar o texto
'149.92' — resultado plausível o bastante para passar despercebido.
O nome técnico desse comportamento é coerção de tipo. Os operadores
aritméticos não seguem todos a mesma regra: o + pode concatenar, enquanto os
outros tentam converter os operandos em número. Antes de rodar cada conta do
pedido, marque o tipo de cada lado; a saída confirma se houve cálculo ou
concatenação.
const preco = 89.9;
const quantidade = 3;
console.log('subtotal:', preco * quantidade);
console.log('com frete:', preco * quantidade + 24.9);
console.log('com desconto:', preco * quantidade - 30);
console.log('preço médio:', 269.7 / quantidade);
console.log('sobra na caixa:', 17 % 4);
console.log('juros de 3 meses:', 1.02 ** 3);Guarde esses rabinhos de 00000005 — eles voltam mais abaixo, e não são erro de
digitação.
| operador | nome | exemplo | resultado |
|---|---|---|---|
+ |
soma ou concatenação | 89.9 + 24.9 |
114.80000000000001 |
- |
subtração | 269.7 - 30 |
239.7 |
* |
multiplicação | 89.9 * 3 |
269.70000000000005 |
/ |
divisão | 269.7 / 3 |
89.89999999999999 |
% |
resto da divisão | 17 % 4 |
1 |
** |
exponenciação | 1.02 ** 3 |
1.0612080000000002 |
++ -- |
incremento e decremento | qtd++ |
qtd vira qtd + 1 |
O + é dois operadores usando o mesmo símbolo
Se os dois lados são números, ele soma. Se qualquer um dos lados é string, ele vira concatenação de texto:
const quantidade = '10';
const brinde = 5;
console.log(quantidade + brinde);
console.log(typeof (quantidade + brinde));Dez mais cinco deu cento e cinco. Nenhum erro, nenhum aviso: o programa continua rodando com um valor absurdo dentro dele. Esse é o comportamento mais perigoso desta lição, porque falha em silêncio.
-, *, / e % forçam número
Os outros operadores não têm essa dupla personalidade. Eles convertem os dois
lados para número antes de calcular — e, quando a conversão não dá certo,
devolvem NaN:
const quantidade = '10';
console.log(quantidade - 5);
console.log(quantidade * 2);
console.log(quantidade / 4);
console.log('1.999,90' - 0);
console.log(typeof ('1.999,90' - 0));Repare na quarta linha. '1.999,90' é um preço perfeitamente legível para um
brasileiro e completamente ilegível para o motor: ponto de milhar e vírgula
decimal não fazem parte da gramática numérica do JavaScript. O resultado é NaN
— que, apesar do nome, tem typeof igual a number.
Resto (%) não é porcentagem
O símbolo engana. % devolve o que sobra de uma divisão inteira, e é a
ferramenta certa para “quantas caixas fecham e quanto sobra”:
const itens = 17;
const porCaixa = 4;
console.log('caixas cheias:', Math.floor(itens / porCaixa));
console.log('sobra:', itens % porCaixa);
console.log('par?', 17 % 2 === 0);
console.log(-7 % 3);
console.log(7 % -3);
console.log(((-7 % 3) + 3) % 3);As duas linhas do meio são a pegadinha: o resto em JavaScript herda o sinal do
dividendo, não do divisor. -7 % 3 dá -1, não 2. Isso quebra código de
paginação circular e de alternância de linhas quando o índice pode ficar
negativo — o caso clássico é um carrossel voltando do primeiro slide para o
último. A correção é a última linha: some o divisor e tire o resto de novo.
Exponenciação (**) e a associatividade à direita
O ** chegou no ES2016 e substituiu Math.pow. Ele é útil em qualquer conta de
juros compostos:
const valor = 1200;
const jurosMes = 0.0199;
console.log(2 ** 10);
console.log(valor * (1 + jurosMes) ** 12);
console.log(Math.round(valor * (1 + jurosMes) ** 12 * 100) / 100);
console.log(2 ** 3 ** 2);A última linha merece atenção: 2 ** 3 ** 2 é 2 ** (3 ** 2), ou seja 2 ** 9,
e não (2 ** 3) ** 2. Diferente dos outros operadores, o ** associa da direita
para a esquerda. Quando houver dúvida, use parênteses — quem lê o código depois
agradece.
0.1 + 0.2 e o dinheiro do checkout
Agora os rabinhos. Números com casas decimais em JavaScript são de ponto
flutuante binário de 64 bits (IEEE 754). Nesse formato, 0.1 não tem
representação exata — do mesmo jeito que 1/3 não tem representação decimal
exata.
console.log(0.1 + 0.2);
console.log(0.1 + 0.2 === 0.3);
const carrinho = [19.9, 19.9, 19.9];
const total = carrinho.reduce((soma, preco) => soma + preco, 0);
console.log(total);
console.log(total === 59.7);
console.log(total.toFixed(2));Três camisetas de R$ 19,90 somam R$ 59,70 na vida real e 59.699999999999996 na
memória. O toFixed(2) esconde isso na hora de mostrar, mas a comparação
total === 59.7 continua falsa — e é aí que uma regra de frete grátis, um
fechamento de caixa ou um teste automatizado começam a falhar sem explicação.
A saída profissional é trabalhar em centavos, com inteiros, e dividir por 100 só na exibição:
const carrinhoEmCentavos = [1990, 1990, 1990];
const totalCentavos = carrinhoEmCentavos.reduce((soma, c) => soma + c, 0);
console.log(totalCentavos);
console.log(totalCentavos === 5970);
console.log((totalCentavos / 100).toFixed(2));
console.log(new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(totalCentavos / 100));Inteiro é exato em JavaScript até 9.007.199.254.740.991
(Number.MAX_SAFE_INTEGER). Nenhuma loja brasileira chega perto disso em
centavos.
++, -- e a atribuição composta
let itensNoCarrinho = 2;
itensNoCarrinho += 3;
console.log(itensNoCarrinho);
itensNoCarrinho++;
console.log(itensNoCarrinho);
let estoque = 6;
console.log('pos-fixado:', estoque--);
console.log('depois:', estoque);
let reservado = 6;
console.log('pre-fixado:', --reservado);estoque-- devolve o valor antes de decrementar; --reservado devolve
depois. Só use as duas formas dentro de uma expressão maior quando a diferença
for intencional — na maioria das vezes estoque -= 1 numa linha própria é mais
honesto. Vale lembrar que ++ e -- exigem uma variável reatribuível: com
const você toma um TypeError, como mostra a lição de
variáveis em JavaScript.
Erros comuns
O bug que não lança erro: "10" + 5
Este é o mais caro, porque nada quebra:
// campoQuantidade veio de um <input>: sempre chega como string
const campoQuantidade = '10';
const brinde = 5;
const totalDeItens = campoQuantidade + brinde;
const precoUnitario = 12.5;
console.log('itens no pedido:', totalDeItens);
console.log('valor a cobrar:', totalDeItens * precoUnitario);O pedido de 15 itens virou uma cobrança de R$ 1.312,50. Note que a segunda linha
“funcionou”: o * converteu a string '105' de volta para número. O erro entrou
uma linha antes e passou despercebido.
Converta na entrada, não no meio da conta: Number(campoQuantidade) ou o
unário +campoQuantidade. E confira o resultado com Number.isNaN antes de
seguir — comparar com NaN usando === nunca funciona, pelo motivo que a lição
de comparação e igualdade explica.
O erro que lança: misturar BigInt com número comum
const totalCentavos = 19970n;
const cupom = 0.1;
console.log(totalCentavos - 500n);
console.log(totalCentavos * cupom);TypeError: Cannot mix BigInt and other types, use explicit conversions at file:///private/tmp/club-store/bigint-centavos.mjs:5:27 at ModuleJob.run (node:internal/modules/esm/module_job:439:25) at async node:internal/modules/esm/loader:633:26 at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5)
Node.js v24.16.0
A primeira conta passou porque os dois lados eram BigInt. A segunda quebrou
porque 0.1 é Number. Esse é o oposto exato do + com string: aqui a
linguagem se recusa a converter em silêncio, justamente para não perder
precisão. Se você adotar BigInt em centavos, ele precisa valer para o cálculo
inteiro.
Boas práticas para conta que envolve dinheiro
- Converta na fronteira. Todo dado que vem de formulário,
JSONou URL é string. Passe porNumber()no ponto de entrada e valide ali mesmo. - Some centavos, formate reais. Guarde inteiros, calcule inteiros, e use
Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' })só na exibição. - Nunca compare valores com casas decimais usando
===. Compare inteiros de centavos, ou tolere uma margem:Math.abs(a - b) < 0.005. - Use parênteses em
**e em expressões com%. Custa dois caracteres e evita um bug de associatividade. - Desconfie de qualquer
+cujos operandos você não escreveu. Se um dos lados veio de fora, ele pode ser string.
O próximo passo da trilha de JavaScript é comparar
esses valores em vez de somá-los — e lá a coerção que você viu no + aparece de
novo, com regras diferentes. Se quiser ver como o mesmo texto vira preço
formatado sem concatenação manual, a lição de
strings em JavaScript trata disso, e o
guia completo de JavaScript mostra onde cada assunto
entra na sequência.
Perguntas frequentes
Por que 0.1 + 0.2 não dá 0.3 em JavaScript?
Qual a diferença entre / e %?
Preciso usar BigInt para trabalhar com dinheiro?
toFixed arredonda certo?
Dúvidas e comentários
Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.
Entrar para perguntarÉ o mesmo login gratuito dos cursos.
Nenhuma dúvida por aqui ainda — a primeira pode ser a sua.
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
- MDN — Expressões e operadores — developer.mozilla.org
- MDN — Number.prototype.toFixed() — developer.mozilla.org
- ECMAScript 2026 Language Specification — Exponentiation Operator — tc39.es


