Operadores aritméticos, de comparação e lógicos
por Callahan WhitmoreEntenda 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.
/devolve decimal quando a divisão não é exata;Math.floor(a / b)dá a parte inteira ea % bdá o resto.- Precedência:
**antes de*,/e%, que vêm antes de+e-; na dúvida, use parênteses. x += 5é o mesmo quex = x + 5; vale para-=,*=,/=,%=e**=, ex++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
// 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
// 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
// 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.
- Calcular as caixas cheias com
Math.floore uma divisão. - Calcular as canetas avulsas com o operador
%. - Guardar cada resultado em uma
conste imprimir com template literals. - A saída deve ser idêntica à saída esperada.
const canetas = 137;
const porCaixa = 12;
const precoCaixa = 30;
// calcule caixas cheias, avulsas, quantas faltam e o valor das caixas
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
- 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.
canetas / porCaixadá11.416..., porque a divisão em JavaScript sempre devolve o resultado com decimais.Math.floordescarta a parte fracionária e sobra o número de caixas cheias: 11.canetas % porCaixadevolve o resto da divisão inteira: 11 caixas de 12 somam 132 canetas, e 137 - 132 = 5 canetas avulsas.porCaixa - avulsasmostra quantas canetas faltam para completar mais uma caixa: 12 - 5 = 7.caixasCheias * precoCaixacalcula o valor só das caixas fechadas, já que as avulsas não formam uma caixa vendável.- Os cinco
console.logmontam 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 / porCaixaimprime11.416666666666666caixas, um número que não existe no estoque. Arredonde para baixo comMath.floor. - Arredondar com
Math.round:Math.round(137 / 12)dá11aqui, mas com 143 canetas daria12, contando uma caixa que não está cheia. Para "quantas cabem inteiras", useMath.floor. - Confundir
%com porcentagem:137 % 12nã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 - avulsasdaria12, 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).
- Calcular a média ponderada em uma única expressão, com parênteses para dividir pela soma dos pesos.
- Guardar
aprovadoerecuperacaoem constantes usando&&e!, semif. - 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.
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
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
- 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.
- Na média, os produtos
prova * 3,trabalho * 2eparticipacao * 1sã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. - A frequência usa dois níveis de parênteses: primeiro
aulas - faltas(31 aulas presentes), depois a divisão poraulase, por fim, a multiplicação por 100, que dá 77.5. media >= 7 && frequencia >= 75junta duas comparações:aprovadosó étruequando as duas são verdadeiras.!aprovado && media >= 5inverte o resultado anterior com!e exige média mínima de 5 para a recuperação. Como o aluno foi aprovado,!aprovadoéfalsee o&&nem avalia o lado direito.- 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 / 6divide 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:
/ 3ignora os pesos e dá15.5. Na média ponderada, o divisor é a soma dos pesos. - Usar
||no lugar de&&:media >= 7 || frequencia >= 75aprovaria 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 * 100dá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).
- Detectar o crítico com
%,===,&&e!, e aplicar o dano com*=e-=. - Declarar
pocoessem 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.
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
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
- 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 guardaundefined. ataqueInimigo - defesadá o dano base, 9.turno % 3 === 0testa se o turno é múltiplo de 3 (6 % 3 dá 0), e&& !escudoAtivoexige também o escudo baixado.- Com o crítico confirmado,
dano *= 2dobra o dano para 18, evida -= danodesconta o golpe: 32 - 18 = 14. pocoes ??= 1só atribui porquepocoeséundefined. Se o save tivesse0poções, o??=manteria o zero, já que ele só trocanulleundefined.- A condição
vida <= vidaMax / 2 && pocoes > 0divide primeiro (20), compara depois e só então aplica o&&: as duas partes são verdadeiras, então a poção é usada. Math.min(vida + 15, vidaMax)cura 15 pontos sem passar do máximo, epocoes--gasta a poção.xp += 30soma a experiência do turno. Em100 * nivel ** 2, a potência é calculada antes da multiplicação: 2² = 4, vezes 100, dá 400.- As duas últimas linhas imprimem comparações diretamente no template literal: 410 >= 400 dá
truee 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 ||= 1também daria 1 aqui, mas um jogador que gastou todas as poções (0) ganharia uma de graça, porque0é falsy. Para "só se não existir", use??=. - Escrever a potência ao contrário:
(100 * nivel) ** 2dá40000em vez de400. Sem parênteses,**já é calculado antes de*. - Somar a cura sem limite:
vida += 15funciona neste turno (29), mas com 30 de vida o herói ficaria com 45 de 40.Math.mingarante o teto. - Testar múltiplo com divisão:
turno / 3 === 0só é 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