Sua dose diária de programação

Conceito, 3 exemplos e 3 exercícios: uma pequena conquista de cada vez.

✦   Lição

Operadores aritméticos, de comparação e lógicos

Operadores são os símbolos que fazem contas e comparações. Os aritméticos são +, -, *, /, % (resto da divisão) e ** (potência). Em JavaScript a divisão sempre devolve decimal quando não é exata: 7 / 2 dá 3.5. Para a parte inteira, use Math.floor; para o que sobrou, use %. A precedência segue a matemática (** antes de * e /, que vêm antes de + e -), e parênteses mudam a ordem quando preciso.

JavaScript Fundamentos Iniciante 3 exemplos + 3 exercícios
42 lições publicadas 10 linguagens novas lições todo dia útil
JavaScript / Fundamentos

Operadores aritméticos, de comparação e lógicos

por Callahan Whitmore
Iniciante

Entenda o conceito

Operadores são os símbolos que fazem contas e comparações. Os aritméticos são +, -, *, /, % (resto da divisão) e ** (potência). Em JavaScript a divisão sempre devolve decimal quando não é exata: 7 / 2 dá 3.5. Para a parte inteira, use Math.floor; para o que sobrou, use %. A precedência segue a matemática (** antes de * e /, que vêm antes de + e -), e parênteses mudam a ordem quando preciso.

As atribuições compostas (+=, -=, *=, /=, %=, **=) fazem a conta e guardam o resultado na própria variável, e ++ e -- somam ou subtraem 1. Os operadores de comparação (===, !==, <, >, <=, >=) sempre devolvem true ou false. Com duas strings, < e > comparam caractere por caractere, então '1250' < '980' dá true.

Os lógicos &&, || e ! combinam condições, mas && e || devolvem um dos operandos, não necessariamente um boolean, e param assim que o resultado está decidido (curto-circuito). Deles nascem as atribuições lógicas: ||= atribui se o valor atual é falsy, &&= se é truthy e ??= se é null ou undefined.

↳ Pontos-chave
  • / devolve decimal quando a divisão não é exata; Math.floor(a / b) dá a parte inteira e a % b dá o resto.
  • Precedência: ** antes de *, / e %, que vêm antes de + e -; na dúvida, use parênteses.
  • x += 5 é o mesmo que x = x + 5; vale para -=, *=, /=, %= e **=, e x++ soma 1.
  • Comparar strings com < e > segue o código de cada caractere: maiúsculas vêm antes de minúsculas e '1250' < '980' dá true.
  • && e || devolvem um dos operandos e param no primeiro que decide; ||=, &&= e ??= atribuem só quando o operador permite.

Do conceito à prática

3 exemplos para ver a ideia funcionando.

01Aritmética na cozinha: porções, caixas e potência

cookies.js
// Operadores aritméticos ao adaptar uma receita de cookies.

const ovosPorReceita = 3;
const farinhaPorReceita = 250; // gramas
const receitas = 5;

// + - * e / funcionam como na matemática
const ovos = ovosPorReceita * receitas;
const farinha = farinhaPorReceita * receitas;
console.log(`Ovos: ${ovos} | farinha: ${farinha} g`);
console.log('Farinha em kg:', farinha / 1000);

// A divisão sempre devolve decimal; para a parte inteira use Math.floor
const ovosPorCaixa = 12;
const caixas = Math.floor(ovos / ovosPorCaixa);
// % devolve o resto da divisão
const ovosSoltos = ovos % ovosPorCaixa;
console.log(`Caixas fechadas: ${caixas} | ovos soltos: ${ovosSoltos}`);

// 50 cookies em pacotes de 6: quantos pacotes e quantos sobram?
const cookies = 50;
console.log(`Pacotes: ${Math.floor(cookies / 6)} | sobram: ${cookies % 6}`);

// ** é a potência: a forma redonda de 10 cm de raio tem área PI * r²
const raio = 10;
const area = Math.PI * raio ** 2;
console.log('Área da forma:', area.toFixed(1), 'cm²');

// Precedência: ** antes de * e /, que vêm antes de + e -
console.log('2 + 3 * 4 =', 2 + 3 * 4);
console.log('(2 + 3) * 4 =', (2 + 3) * 4);

Multiplicar os ingredientes por 5 receitas usa *, e converter gramas em quilos usa /, que devolve 1.25 sem arredondar nada. Para saber quantas caixas de 12 ovos cabem nos 15 ovos, Math.floor(15 / 12) pega a parte inteira (1) e 15 % 12 devolve o resto (3 ovos soltos). A mesma dupla resolve os pacotes de cookies.

O ** calcula a potência do raio na área da forma, e as duas últimas linhas mostram a precedência: sem parênteses, 3 * 4 é feito antes da soma; com parênteses, a soma vem primeiro.

02Comparações no placar de um campeonato

placar.js
// Operadores de comparação no placar de um campeonato de jogos.

const pontosAna = 1250;
const pontosBruno = 980;
const recorde = 1250;

// Toda comparação devolve true ou false
console.log('Ana passou de Bruno?', pontosAna > pontosBruno);
console.log('Ana bateu o recorde?', pontosAna > recorde);
console.log('Ana igualou o recorde?', pontosAna >= recorde);
console.log('Bruno abaixo de mil?', pontosBruno < 1000);

// === compara valor e tipo; == converte os tipos antes de comparar
const pontosDigitados = '980';
console.log('980 == "980":', pontosBruno == pontosDigitados);
console.log('980 === "980":', pontosBruno === pontosDigitados);
console.log('980 !== "980":', pontosBruno !== pontosDigitados);

// <, > com strings comparam letra por letra, pelo código de cada caractere
console.log('"Ana" < "Bruno":', 'Ana' < 'Bruno');
console.log('"ana" < "Bruno":', 'ana' < 'Bruno'); // minúsculas vêm depois
// Texto com números compara como texto: "1" vem antes de "9"
console.log('"1250" < "980":', '1250' < '980');
console.log('1250 < 980:', 1250 < 980);

// NaN não é igual a nada, nem a ele mesmo
const pontosInvalidos = Number('mil');
console.log('NaN === NaN:', pontosInvalidos === pontosInvalidos);
console.log('É NaN?', Number.isNaN(pontosInvalidos));

As primeiras comparações mostram a diferença entre > e >=: Ana não passou do recorde, mas igualou. Depois, o número 980 é comparado com o texto '980': o == converte o texto antes e diz que são iguais, enquanto o === exige o mesmo tipo e diz que não são.

Com strings, < compara caractere por caractere pelo código de cada um. Por isso 'ana' < 'Bruno' é false (letras minúsculas têm códigos maiores) e '1250' < '980' é true (o '1' vem antes do '9'). Por fim, NaN não é igual a nada, nem a ele mesmo; Number.isNaN é o teste confiável.

03Atribuições compostas e lógicas no cadastro da biblioteca

cadastro.js
// Operadores lógicos e atribuições compostas no cadastro da biblioteca.

const nome = 'Caio';
let livros = 2;
let multa = 0;
let apelido = ''; // campo deixado em branco
let email; // ainda não informado

// Atribuição composta: += -= *= /= %= **= operam e guardam na mesma variável
livros += 1; // pegou mais um livro
multa += 2.5; // um dia de atraso
multa *= 3; // três dias de atraso
console.log(`Livros: ${livros} | multa: R$ ${multa.toFixed(2)}`);

// ++ e -- somam ou subtraem 1 da variável
livros--; // devolveu um livro
console.log('Livros depois da devolução:', livros);

// && e || devolvem um dos operandos, não necessariamente um boolean
console.log("'Caio' && 5 ->", 'Caio' && 5);
console.log("'' || 'sem apelido' ->", '' || 'sem apelido');

// Curto-circuito: o lado direito só é avaliado se for preciso
const temMulta = multa > 0;
const aviso = temMulta && 'há multa pendente';
console.log('Aviso:', aviso);

// Precedência: ! antes de &&, que vem antes de ||
const liberado = !temMulta || (multa < 10 && livros < 3);
console.log('Liberado para novo empréstimo?', liberado);

// Atribuições lógicas: só atribuem quando o operador permite
apelido ||= nome; // '' é falsy, então recebe o nome
email ??= 'não informado'; // só troca null ou undefined
multa &&= Math.ceil(multa); // só arredonda se já houver multa
console.log(`Apelido: ${apelido} | e-mail: ${email} | multa: R$ ${multa}`);

livros += 1, multa += 2.5 e multa *= 3 fazem a conta e guardam o resultado na própria variável, e livros-- subtrai 1. Em seguida, 'Caio' && 5 devolve 5 e '' || 'sem apelido' devolve o texto: && e || entregam um dos operandos, e o curto-circuito faz temMulta && 'há multa pendente' só avaliar o lado direito quando há multa.

Na regra de liberação, ! é aplicado primeiro e && antes de ||; os parênteses só deixam isso visível. As atribuições lógicas fecham o exemplo: apelido ||= nome troca a string vazia, email ??= ... preenche o valor undefined e multa &&= Math.ceil(multa) só arredonda porque já existe multa.


Agora é com você

Tente resolver no rascunho antes de abrir a resposta. Os códigos não são executados nesta página.

01Canetas em caixas fechadasFácil

O estoque da loja tem 137 canetas, vendidas em caixas de 12 a R$ 30 cada. Calcule quantas caixas cheias dá para montar, quantas canetas ficam avulsas, quantas faltam para fechar mais uma caixa e o valor das caixas cheias.

↳ Requisitos
  • Calcular as caixas cheias com Math.floor e uma divisão.
  • Calcular as canetas avulsas com o operador %.
  • Guardar cada resultado em uma const e imprimir com template literals.
  • A saída deve ser idêntica à saída esperada.
código inicial
const canetas = 137;
const porCaixa = 12;
const precoCaixa = 30;

// calcule caixas cheias, avulsas, quantas faltam e o valor das caixas
saída esperada
Canetas no estoque: 137
Caixas cheias: 11
Canetas avulsas: 5
Faltam 7 para outra caixa
Valor das caixas: R$ 330
Ver dica

Pense em como você faria a divisão na escola, com quociente e resto: cada parte tem seu operador.

Ver resposta
// Canetas do estoque separadas em caixas de 12
const canetas = 137;
const porCaixa = 12;
const precoCaixa = 30;

const caixasCheias = Math.floor(canetas / porCaixa); // só a parte inteira
const avulsas = canetas % porCaixa; // o resto da divisão
const faltam = porCaixa - avulsas; // para fechar mais uma caixa
const valorCaixas = caixasCheias * precoCaixa;

console.log(`Canetas no estoque: ${canetas}`);
console.log(`Caixas cheias: ${caixasCheias}`);
console.log(`Canetas avulsas: ${avulsas}`);
console.log(`Faltam ${faltam} para outra caixa`);
console.log(`Valor das caixas: R$ ${valorCaixas}`);

Passo a passo

  1. As três constantes do início guardam os dados do problema: total de canetas, quantas cabem numa caixa e o preço de cada caixa fechada.
  2. canetas / porCaixa dá 11.416..., porque a divisão em JavaScript sempre devolve o resultado com decimais. Math.floor descarta a parte fracionária e sobra o número de caixas cheias: 11.
  3. canetas % porCaixa devolve o resto da divisão inteira: 11 caixas de 12 somam 132 canetas, e 137 - 132 = 5 canetas avulsas.
  4. porCaixa - avulsas mostra quantas canetas faltam para completar mais uma caixa: 12 - 5 = 7.
  5. caixasCheias * precoCaixa calcula o valor só das caixas fechadas, já que as avulsas não formam uma caixa vendável.
  6. Os cinco console.log montam o relatório com template literals.

Por que funciona

Divisão e resto são as duas metades da mesma conta: Math.floor(a / b) diz quantas vezes b cabe inteiro em a, e a % b diz o que sobrou. Juntas, elas sempre reconstroem o valor original (11 * 12 + 5 = 137), por isso servem para qualquer problema de "agrupar em pacotes", seja caneta em caixa, minuto em hora ou centavo em real.

Erros comuns

  • Usar só a divisão: canetas / porCaixa imprime 11.416666666666666 caixas, um número que não existe no estoque. Arredonde para baixo com Math.floor.
  • Arredondar com Math.round: Math.round(137 / 12) dá 11 aqui, mas com 143 canetas daria 12, contando uma caixa que não está cheia. Para "quantas cabem inteiras", use Math.floor.
  • Confundir % com porcentagem: 137 % 12 não é "137 por cento de 12"; é o resto da divisão. Para porcentagem, multiplique e divida por 100.
  • Esquecer o caso sem sobra: se o estoque fosse 132, porCaixa - avulsas daria 12, como se faltasse uma caixa inteira. Quando isso importar, use (porCaixa - avulsas) % porCaixa, que dá 0.

Outra forma de resolver

Math.trunc(canetas / porCaixa) também descarta os decimais e dá o mesmo resultado para números positivos. A diferença aparece com negativos: Math.floor(-1.5) dá -2 e Math.trunc(-1.5) dá -1. Para contar caixas, que nunca são negativas, as duas servem; Math.floor é a escolha mais comum.

Saída esperada

Canetas no estoque: 137
Caixas cheias: 11
Canetas avulsas: 5
Faltam 7 para outra caixa
Valor das caixas: R$ 330

02Média ponderada e frequência no boletimMédio

Um aluno tirou 7.5 na prova (peso 3), 9 no trabalho (peso 2) e 6 em participação (peso 1), e faltou a 9 de 40 aulas. Calcule a média ponderada e a frequência em porcentagem e diga se ele foi aprovado (média a partir de 7 e frequência a partir de 75%) ou se vai para a recuperação (não aprovado e média a partir de 5).

↳ Requisitos
  • Calcular a média ponderada em uma única expressão, com parênteses para dividir pela soma dos pesos.
  • Guardar aprovado e recuperacao em constantes usando && e !, sem if.
  • Imprimir também o resultado de cada comparação isolada (média e frequência).
  • A saída deve ser idêntica à saída esperada.
código inicial
const prova = 7.5; // peso 3
const trabalho = 9; // peso 2
const participacao = 6; // peso 1
const aulas = 40;
const faltas = 9;

// calcule media e frequencia, depois aprovado e recuperacao
saída esperada
Média ponderada: 7.75
Frequência: 77.5%
Média suficiente? true
Frequência suficiente? true
Aprovado? true
Recuperação? false
Ver dica

Em uma média ponderada, cada nota é multiplicada pelo seu peso e o total é dividido pela soma dos pesos, não pela quantidade de notas.

Ver resposta
// Média ponderada e frequência de um aluno no boletim
const prova = 7.5; // peso 3
const trabalho = 9; // peso 2
const participacao = 6; // peso 1
const aulas = 40;
const faltas = 9;

// Parênteses: primeiro a soma dos produtos, depois a divisão
const media = (prova * 3 + trabalho * 2 + participacao * 1) / (3 + 2 + 1);
const frequencia = ((aulas - faltas) / aulas) * 100;

// Comparações combinadas com operadores lógicos
const aprovado = media >= 7 && frequencia >= 75;
const recuperacao = !aprovado && media >= 5;

console.log(`Média ponderada: ${media}`);
console.log(`Frequência: ${frequencia}%`);
console.log(`Média suficiente? ${media >= 7}`);
console.log(`Frequência suficiente? ${frequencia >= 75}`);
console.log(`Aprovado? ${aprovado}`);
console.log(`Recuperação? ${recuperacao}`);

Passo a passo

  1. As cinco constantes guardam as notas e a contagem de aulas; os pesos aparecem nos comentários e direto na conta, para que a fórmula fique legível.
  2. Na média, os produtos prova * 3, trabalho * 2 e participacao * 1 são calculados primeiro, porque * tem precedência maior que +. A soma fica entre parênteses para ser dividida inteira pela soma dos pesos: 46.5 / 6 = 7.75.
  3. A frequência usa dois níveis de parênteses: primeiro aulas - faltas (31 aulas presentes), depois a divisão por aulas e, por fim, a multiplicação por 100, que dá 77.5.
  4. media >= 7 && frequencia >= 75 junta duas comparações: aprovado só é true quando as duas são verdadeiras.
  5. !aprovado && media >= 5 inverte o resultado anterior com ! e exige média mínima de 5 para a recuperação. Como o aluno foi aprovado, !aprovado é false e o && nem avalia o lado direito.
  6. As linhas de "suficiente?" imprimem as comparações isoladas, mostrando que cada uma também é um valor boolean.

Por que funciona

Operadores aritméticos seguem a precedência da matemática, e os parênteses forçam a ordem quando a regra natural não serve, como na divisão da soma inteira. As comparações transformam números em true ou false, e os operadores lógicos combinam esses booleans numa regra de negócio, sem precisar de nenhum if: cada constante já guarda a resposta pronta.

Erros comuns

  • Esquecer os parênteses da soma: prova * 3 + trabalho * 2 + participacao * 1 / 6 divide só o último termo por 6 e dá 41.5. Envolva a soma inteira em parênteses antes de dividir.
  • Dividir pelo número de notas: / 3 ignora os pesos e dá 15.5. Na média ponderada, o divisor é a soma dos pesos.
  • Usar || no lugar de &&: media >= 7 || frequencia >= 75 aprovaria quem tem média 9 e 50% de frequência. As duas condições são obrigatórias, então o operador é &&.
  • Calcular a frequência com as faltas: faltas / aulas * 100 dá 22.5, que é a porcentagem de ausência. Subtraia as faltas do total antes de dividir.

Outra forma de resolver

Guardar os pesos em constantes (const pesoProva = 3;) e escrever (prova * pesoProva + ...) / (pesoProva + pesoTrabalho + pesoParticipacao) evita números soltos na fórmula. Vale a pena quando os pesos podem mudar de um bimestre para outro, porque basta alterar uma linha.

Saída esperada

Média ponderada: 7.75
Frequência: 77.5%
Média suficiente? true
Frequência suficiente? true
Aprovado? true
Recuperação? false

03Turno de batalha no RPGDesafio

No turno 6, um inimigo com ataque 13 acerta o herói (vida 32 de 40, defesa 4, escudo baixado); o golpe é crítico e dobra o dano quando o turno é múltiplo de 3 e o escudo está baixado. Desconte o dano, dê 1 poção de cortesia se o save não tiver o campo, use-a (cura 15, sem passar de 40) se a vida estiver na metade ou abaixo, some 30 de XP aos 380 atuais e diga se o herói subiu do nível 2 (o próximo nível pede 100 * nível² de XP).

↳ Requisitos
  • Detectar o crítico com %, ===, && e !, e aplicar o dano com *= e -=.
  • Declarar pocoes sem valor e preenchê-la com ??=; gastar a poção com --.
  • Calcular o XP do próximo nível com o operador ** e imprimir as comparações de nível e de derrota direto no template literal.
  • A saída deve ser idêntica à saída esperada.
código inicial
const vidaMax = 40;
let vida = 32;
const ataqueInimigo = 13;
const defesa = 4;
const turno = 6;
const escudoAtivo = false;
let pocoes; // save antigo, sem esse campo
let xp = 380;
const nivel = 2;

// resolva o golpe, a poção e a experiência do turno
saída esperada
Turno 6: crítico? true
Dano recebido: 18
Vida após o golpe: 14/40
Poção usada: vida 29/40, poções restantes: 0
XP: 410/400 | subiu de nível? true
Derrotado? false
Ver dica

Um número é múltiplo de outro quando o resto da divisão entre eles é zero.

Ver resposta
// Um turno de batalha resolvido só com operadores
const vidaMax = 40;
let vida = 32;
const ataqueInimigo = 13;
const defesa = 4;
const turno = 6;
const escudoAtivo = false;
let pocoes; // save antigo, sem esse campo
let xp = 380;
const nivel = 2;

// Dano base e crítico a cada 3 turnos, se o escudo estiver baixado
let dano = ataqueInimigo - defesa;
const critico = turno % 3 === 0 && !escudoAtivo;
if (critico) {
  dano *= 2;
}
vida -= dano;
console.log(`Turno ${turno}: crítico? ${critico}`);
console.log(`Dano recebido: ${dano}`);
console.log(`Vida após o golpe: ${vida}/${vidaMax}`);

// Save antigo sem poções recebe 1 de cortesia
pocoes ??= 1;
if (vida <= vidaMax / 2 && pocoes > 0) {
  vida = Math.min(vida + 15, vidaMax); // nunca passa do máximo
  pocoes--;
  console.log(`Poção usada: vida ${vida}/${vidaMax}, poções restantes: ${pocoes}`);
}

// Experiência: o próximo nível pede 100 * nível²
xp += 30;
const xpProximo = 100 * nivel ** 2;
console.log(`XP: ${xp}/${xpProximo} | subiu de nível? ${xp >= xpProximo}`);
console.log(`Derrotado? ${vida <= 0}`);

Passo a passo

  1. As constantes descrevem o turno e a vida usa let, porque vai mudar várias vezes. pocoes é declarada sem valor para imitar um save antigo em que o campo não existia, e por isso guarda undefined.
  2. ataqueInimigo - defesa dá o dano base, 9. turno % 3 === 0 testa se o turno é múltiplo de 3 (6 % 3 dá 0), e && !escudoAtivo exige também o escudo baixado.
  3. Com o crítico confirmado, dano *= 2 dobra o dano para 18, e vida -= dano desconta o golpe: 32 - 18 = 14.
  4. pocoes ??= 1 só atribui porque pocoes é undefined. Se o save tivesse 0 poções, o ??= manteria o zero, já que ele só troca null e undefined.
  5. A condição vida <= vidaMax / 2 && pocoes > 0 divide primeiro (20), compara depois e só então aplica o &&: as duas partes são verdadeiras, então a poção é usada.
  6. Math.min(vida + 15, vidaMax) cura 15 pontos sem passar do máximo, e pocoes-- gasta a poção.
  7. xp += 30 soma a experiência do turno. Em 100 * nivel ** 2, a potência é calculada antes da multiplicação: 2² = 4, vezes 100, dá 400.
  8. As duas últimas linhas imprimem comparações diretamente no template literal: 410 >= 400 dá true e 29 <= 0 dá false.

Por que funciona

Cada regra do jogo virou uma expressão com os operadores certos: % detecta o ritmo dos turnos, as atribuições compostas (*=, -=, +=, --) atualizam o estado sem repetir o nome da variável, ??= preenche só o que está ausente e a precedência (** antes de *, aritmética antes de comparação, comparação antes de &&) permite escrever as condições sem parênteses extras. O resultado é um turno inteiro resolvido com poucas linhas e nenhuma conta feita à mão.

Erros comuns

  • Usar ||= para a poção: pocoes ||= 1 também daria 1 aqui, mas um jogador que gastou todas as poções (0) ganharia uma de graça, porque 0 é falsy. Para "só se não existir", use ??=.
  • Escrever a potência ao contrário: (100 * nivel) ** 2 dá 40000 em vez de 400. Sem parênteses, ** já é calculado antes de *.
  • Somar a cura sem limite: vida += 15 funciona neste turno (29), mas com 30 de vida o herói ficaria com 45 de 40. Math.min garante o teto.
  • Testar múltiplo com divisão: turno / 3 === 0 só é verdadeiro no turno 0. O teste de múltiplo é turno % 3 === 0, o resto da divisão.

Outra forma de resolver

O crítico cabe numa linha com o ternário: const dano = (ataqueInimigo - defesa) * (critico ? 2 : 1);. Assim dano pode ser const, o que é preferível quando o valor não muda depois; o *= dentro do if fica melhor quando outras regras (armadura, fraquezas) também vão alterar o dano em sequência.

Saída esperada

Turno 6: crítico? true
Dano recebido: 18
Vida após o golpe: 14/40
Poção usada: vida 29/40, poções restantes: 0
XP: 410/400 | subiu de nível? true
Derrotado? false
← Anterior na trilha de JavaScriptDecisões com condicionais

Últimas lições

Ver o arquivo completo →