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

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.

Rodolfo Mori6 min de leitura

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.

js
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);
subtotal: 269.70000000000005 com frete: 294.6 com desconto: 239.70000000000005 preço médio: 89.89999999999999 sobra na caixa: 1 juros de 3 meses: 1.0612080000000002

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:

js
const quantidade = '10';
const brinde = 5;

console.log(quantidade + brinde);
console.log(typeof (quantidade + brinde));
105 string

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:

js
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));
5 20 2.5 NaN number

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”:

js
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);
caixas cheias: 4 sobra: 1 par? false -1 1 2

As duas linhas do meio são a pegadinha: o resto em JavaScript herda o sinal do dividendo, não do divisor. -7 % 3-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:

js
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);
1024 1520.1006595991125 1520.1 512

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.

js
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));
0.30000000000000004 false 59.699999999999996 false 59.70

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:

js
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));
5970 true 59.70 R$ 59,70

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

js
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);
5 6 pos-fixado: 6 depois: 5 pre-fixado: 5

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:

js
// 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);
itens no pedido: 105 valor a cobrar: 1312.5

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

js
const totalCentavos = 19970n;
const cupom = 0.1;

console.log(totalCentavos - 500n);
console.log(totalCentavos * cupom);
19470n file:///private/tmp/club-store/bigint-centavos.mjs:5 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, JSON ou URL é string. Passe por Number() 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.

  • operadores
  • numeros
  • aritmetica
  • ponto flutuante
  • coercao

Perguntas frequentes

Por que 0.1 + 0.2 não dá 0.3 em JavaScript?
Porque números com casas decimais são guardados em binário de 64 bits, e 0.1 não tem representação exata em binário — assim como 1/3 não tem representação exata em decimal. O erro é minúsculo, mas aparece na comparação. Isso não é bug do JavaScript: qualquer linguagem que use IEEE 754 faz igual, incluindo Python e Java.
Qual a diferença entre / e %?
A barra devolve o quociente com casas decimais, o percentual devolve o resto da divisão inteira. 17 / 4 é 4.25; 17 % 4 é 1. Na prática você usa a barra para média e preço unitário, e o resto para paginação, alternância de linha e "quantas caixas cheias sobram quantos itens".
Preciso usar BigInt para trabalhar com dinheiro?
Na maioria das lojas, não. Guardar o valor em centavos como inteiro comum já resolve, porque números inteiros são exatos até 9 quatrilhões. BigInt só entra quando você passa desse limite ou precisa de precisão arbitrária — e ele não aceita mistura com número comum na mesma conta.
toFixed arredonda certo?
Nem sempre. toFixed arredonda o número binário que existe na memória, não o decimal que você enxerga: 1.005.toFixed(2) devolve "1.00". Para dinheiro, faça a conta em centavos com inteiros e formate só na hora de exibir, com Intl.NumberFormat.

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 — Expressões e operadores — developer.mozilla.org
  2. MDN — Number.prototype.toFixed() — developer.mozilla.org
  3. ECMAScript 2026 Language Specification — Exponentiation Operator — tc39.es

Continue por aqui