Trilha de aprendizado · Nível 9 · Tutorial 3

Cálculos e conversões numéricas em C/C++

Escreva cálculos em C/C++ que respeitem a faixa dos tipos e a ordem das operações, evitando truncamentos e resultados intermediários inadequados.

  • Nível: Iniciante
  • Duração: 25 min
  • 9 passos
Cálculos e conversões numéricas em C/C++

O que você vai percorrer

  1. Organize a expressão antes de calcular Aplique precedência e parênteses para calcular expressões numéricas simples em C/C++. 2 min
  2. Atualize valores com += e -= Use += e -= para atualizar uma variável e acompanhe seu valor após cada instrução. 2 min
  3. Separe quociente e resto Use / para encontrar o quociente inteiro, % para encontrar o resto e confirme que o divisor não é zero. 3 min
  4. Escolha quando preservar a fração Faça uma divisão em ponto flutuante alterando um operando antes do operador `/`, preservando a parte fracionária quando ela for necessária. 3 min
  5. Converta valores de forma intencional Use casts explícitos para controlar quando uma expressão passa a usar ponto flutuante e reconheça o truncamento ao converter um float para inteiro. 3 min
  6. Amplie o tipo antes da operação Corrija multiplicações de inteiros de 16 bits ampliando um operando antes do resultado intermediário. 3 min
  7. Diferencie retorno modular e estouro com sinal Classifique cálculos próximos aos limites, distinguindo retorno modular sem sinal de estouro com sinal sem resultado garantido. 3 min
  8. Evite perdas ao misturar ou reduzir tipos Reconheça conversões implícitas que alteram valores negativos e evite reduzir resultados para destinos que não comportam sua faixa. 3 min
  9. Revise e valide um cálculo completo Corrija um cálculo de tempo, teste valores de fronteira e aplique critérios para reconhecer resultados válidos, aproximações, perdas e operações inválidas. 4 min

O que você vai aprender

  • Calcular o resultado de expressões com operações aritméticas e parênteses.
  • Distinguir divisão inteira de divisão em ponto flutuante.
  • Posicionar uma conversão de tipo antes da operação que exige maior faixa numérica.
  • Identificar por análise divisões por zero, estouros e conversões que perderiam informação.
  • Comparar o resultado esperado de uma expressão em valores centrais e próximos aos limites do tipo.

Antes de começar

  • Variáveis e tipos no firmware
  • Cálculos com unidades e prefixos elétricos

Passo 1 de 9

Organize a expressão antes de calcular

Aplique precedência e parênteses para calcular expressões numéricas simples em C/C++.

Precedência define o agrupamento

Leia a expressão pela precedência

Em C/C++, uma expressão numérica pode combinar +, -, * e /. Neste início, use exemplos em que toda divisão é exata e todos os resultados cabem nos tipos do Uno R3.

A regra principal é:

  • * e / têm precedência sobre + e -.
  • Operadores com a mesma precedência são agrupados da esquerda para a direita.

Assim, 2 + 3 * 4 não é (2 + 3) * 4: primeiro ocorre a multiplicação, depois a soma.

Árvore do cálculo

Árvore que mostra 3 vezes 4 sendo calculado antes de somar 2.

A árvore evidencia o agrupamento: 2 + (3 * 4).

Torne a intenção explícita

Quando a precedência empata

Considere esta atribuição:

int resultado = 18 - 8 / 2 + 3;

A divisão vem primeiro: 8 / 2 resulta em 4. Restam 18 - 4 + 3.

Subtração e adição têm a mesma precedência, portanto o agrupamento é da esquerda para a direita: (18 - 4) + 3. O valor armazenado é 17.

“Da esquerda para a direita” descreve o agrupamento desses operadores. Não use essa frase para tirar conclusões sobre uma ordem geral de avaliação de operandos; aqui os operandos são apenas valores e não têm efeitos colaterais.

Expressão com agrupamento implícito

Trecho para estudar

cpp
int resultado = 18 - 8 / 2 + 3;
// Agrupamento: (18 - (8 / 2)) + 3
// resultado recebe 17

Dica

Parênteses mudam a árvore

Use parênteses quando quiser mudar ou deixar explícito o cálculo pretendido. Por exemplo, (18 - 8) / 2 + 3 agrupa a subtração antes da divisão e resulta em 8.

Parênteses internos são resolvidos antes das operações externas. Eles tornam a intenção legível para você e para quem revisar o código.

Acompanhe cada etapa

Pratique o rastreamento

Ordene as etapas usadas para calcular 20 - 6 / 3 + 2 de acordo com o agrupamento da expressão.

Acompanhe o cálculo

Coloque as etapas na ordem correta.

  1. Calcular 20 - 2 = 18
  2. Calcular 18 + 2 = 20
  3. Calcular 6 / 3 = 2

Use parênteses para agrupar

Expresse o cálculo desejado

Você quer calcular primeiro a soma 7 + 5 e depois multiplicar o total por 3.

Complete as lacunas apenas com os parênteses necessários. A expressão final deve produzir 36.

Complete a expressão

Complete os parênteses para obter o agrupamento solicitado.

cpp
int total = 7 + 5 * 3;

Passo 2 de 9

Atualize valores com += e -=

Use += e -= para atualizar uma variável e acompanhe seu valor após cada instrução.

Some ou subtraia no próprio valor

Atualize o valor armazenado

Uma atribuição composta atualiza a variável usando o valor que ela já armazena.

  • saldo += 3; soma 3 ao valor atual de saldo.
  • saldo -= 2; subtrai 2 do valor atual de saldo.

Em ambos os casos, o novo valor substitui o anterior na própria variável.

O valor muda a cada instrução

Diagrama mostrando uma mesma variável com valor 10, depois 13 após somar 3 e, por fim, 11 após subtrair 2.

Cada instrução usa o valor armazenado naquele instante e grava o resultado de volta na variável.

Dica

Leitura rápida

Leia += como “some e guarde” e -= como “subtraia e guarde”. A atualização ocorre quando a instrução é executada; ela não é uma igualdade matemática.

Compare formas equivalentes

Forma curta e forma expandida

As duas formas são alternativas: cada bloco começa com o mesmo valor inicial e executa somente uma atualização.

cpp
// Forma composta
int pontosA = 12;
pontosA += 5;        // pontosA passa a valer 17

// Forma expandida equivalente
int pontosB = 12;
pontosB = pontosB + 5; // pontosB passa a valer 17

Exemplo

Duas escritas, uma atualização

Com uma variável simples, pontos += 5; tem o mesmo efeito pretendido de pontos = pontos + 5;. Para subtrair, pontos -= 4; corresponde a pontos = pontos - 4;.

A forma composta evita repetir o nome da variável e evidencia que ela será atualizada. Nos exemplos deste step, os resultados cabem no tipo int do Uno R3.

Rastreie linha por linha

Rastreie uma sequência

Acompanhe o valor após cada linha. Comece em 10, some 6 e depois subtraia 4.

cpp
int nivel = 10;
nivel += 6;  // nivel: 16
nivel -= 4;  // nivel: 12

Dica

Rastreamento sequencial

Uma linha depende do valor deixado pela linha anterior. Portanto, não calcule cada atualização a partir do valor inicial.

Confira o rastreamento

Qual valor está armazenado em nivel ao final do código mostrado?

Pratique as atualizações

Complete a instrução

Complete a atualização para que tentativas passe de 2 para 9.

cpp
int tentativas = 2;
tentativas  7;

Calcule o valor final

Qual é o valor final de contador?

int contador = 20;
contador += 5;
contador -= 8;
contador += 3;

Passo 3 de 9

Separe quociente e resto

Use / para encontrar o quociente inteiro, % para encontrar o resto e confirme que o divisor não é zero.

Leia a divisão como agrupamento

Divisão inteira

Quando os dois operandos são inteiros não negativos, / produz o quociente inteiro: a parte que sobraria como fração é descartada.

Em 17 / 5, cabem 3 grupos completos de 5 em 17. Portanto, o resultado é 3, não 3,4.

Use % para obter o resto da mesma divisão: 17 % 5 resulta em 2.

Grupos completos e sobra

Diagrama de 17 unidades separadas em três grupos completos de cinco e duas unidades restantes.

O quociente conta os grupos completos; o resto mostra o que não completou outro grupo.

Exemplo

Confira pela relação

Para operandos não negativos, a relação é:

dividendo = divisor × quociente + resto

Além disso, o resto vai de 0 até um valor menor que o divisor. No exemplo: 17 = 5 × 3 + 2.

Use / e % juntos

Quociente e resto no código

Em C/C++, / e % usam os mesmos dois valores, mas respondem perguntas diferentes:

  • 17 / 5 → quantos grupos completos? Resultado: 3.
  • 17 % 5 → quanto sobrou? Resultado: 2.

Os valores abaixo são inteiros, portanto ambos os resultados também são inteiros.

Calcule as duas partes

Exemplo com 17 dividido em grupos de 5

cpp
int quantidade = 17;
int porGrupo = 5;

int gruposCompletos = quantidade / porGrupo;
int sobra = quantidade % porGrupo;

Dica

Zero como dividendo

Se quantidade fosse 0 e porGrupo fosse 5, os cálculos seriam válidos: 0 / 5 é 0 e 0 % 5 é 0. O dividendo pode ser zero.

Aplique os dois operadores

Pratique quociente e resto

Complete os operadores para guardar o número de grupos completos e a quantidade que sobra. Considere apenas valores inteiros não negativos.

Complete o cálculo

Preencha os operadores adequados.

cpp
int itens = 23;
int tamanhoGrupo = 4;

int grupos = itens  tamanhoGrupo;
int restantes = itens  tamanhoGrupo;

Verifique antes de calcular

Atenção

Nunca divida por zero

Antes de calcular com / ou %, confira o divisor: o valor à direita do operador. Ele precisa ser diferente de zero.

Uma operação como 8 / 0 ou 8 % 0 é inválida. Não tente prever um resultado numérico para ela.

Já 0 / 8 e 0 % 8 são válidas: o divisor é 8, não zero.

Analise o divisor

Qual expressão é inválida por ter divisor igual a zero?

Passo 4 de 9

Escolha quando preservar a fração

Faça uma divisão em ponto flutuante alterando um operando antes do operador /, preservando a parte fracionária quando ela for necessária.

Faça a divisão preservar a fração

Um operando float muda a divisão

Em uma divisão, dois operandos inteiros produzem uma divisão inteira, que descarta a parte fracionária. Para preservá-la, faça pelo menos um operando ser float.

float metade = 5.0f / 2;

Como 5.0f é float, o 2 é convertido implicitamente para float nesta operação. O resultado é aproximadamente 2.5f.

Use ponto em literais no código, nunca vírgula. O sufixo f deixa explícito que o literal é float.

Dois caminhos para a mesma razão

Diagrama comparando 5 dividido por 2 como inteiros, com resultado 2, e 5.0 dividido por 2 com float, com resultado 2.5.

O tipo dos operandos determina se a divisão descarta ou preserva a fração.

Mude a expressão, não só o destino

O destino não recupera a fração

Compare as duas declarações. As duas guardam o resultado em uma variável float, mas somente uma expressão calcula a divisão em ponto flutuante.

Duas expressões, dois cálculos

Compare o tipo dos operandos antes de olhar para a variável de destino.

cpp
float a = 5 / 2;     // a recebe 2.0f
float b = 5.0f / 2;  // b recebe aproximadamente 2.5f

Dica

A ordem importa

Em a, a expressão 5 / 2 ocorre somente entre inteiros e vale 2. Só então esse valor é armazenado como 2.0f.

Em b, 5.0f já é um operando float; por isso, a divisão preserva a fração. O inteiro 2 é convertido implicitamente para participar dessa operação.

Aplique a regra

Pratique: preserve a fração

Considere int pulsos = 7; e int partes = 2;. Complete a expressão para calcular a média como aproximadamente 3.5f. A mudança precisa ocorrer em um operando antes de /.

Altere um operando

Preencha o literal adequado antes do operador /.

cpp
int pulsos = 7;
int partes = 2;
float media =  / partes;

Confira antes de dividir

Identifique o cálculo ocorrido

Associe cada expressão ao valor que chega à variável. Observe primeiro os operandos usados em /; o tipo da variável de destino não refaz uma operação que já terminou.

Qual cálculo ocorreu?

Relacione cada declaração ao resultado correspondente.

Toque em um item e depois no par correspondente.

Atenção

A regra do divisor continua valendo

Antes de qualquer divisão, confirme que o divisor não é zero. Isso vale para divisões inteiras e para ponto flutuante. Não use um resultado especial como forma de tratar um cálculo que começou com divisor igual a zero.

Passo 5 de 9

Converta valores de forma intencional

Use casts explícitos para controlar quando uma expressão passa a usar ponto flutuante e reconheça o truncamento ao converter um float para inteiro.

Converta antes da operação

Conversão implícita e explícita

Uma conversão pode ser implícita, quando a linguagem ajusta um valor para participar de uma expressão ou atribuição, ou explícita, quando você escreve o tipo desejado.

A sintaxe (tipo)expressão funciona nos exemplos de C e C++. Use-a para tornar clara uma decisão importante: converter um operando para float antes de / faz a divisão preservar a fração. Uma atribuição como float valor = 3; também converte 3 implicitamente para 3.0f, mas isso acontece depois que o valor da expressão já foi obtido.

A posição do cast muda o cálculo

Compare o ponto em que cada conversão ocorre.

cpp
int total = 7;
int partes = 2;

float quocienteCorreto = (float)total / partes; // aproximadamente 3.5f
float quocienteTardio = (float)(total / partes); // 3.0f

Dica

O cast precisa vir antes

No primeiro caso, total já é convertido para float antes de /; então partes também participa como float e a fração é preservada. No segundo, total / partes acontece primeiro entre inteiros e produz 3. Converter esse resultado para float gera 3.0f: a fração que não foi calculada não pode ser recuperada.

Entenda o que se perde

De float para inteiro

Ao converter um float para um tipo inteiro, a parte fracionária é descartada em direção a zero. Isso não é arredondamento.

Assim, (int)3.8f produz 3 e (int)-3.8f produz -3. Nos dois exemplos, o resultado cabe em int. Antes de fazer esse tipo de conversão, confira se o valor pode ser representado pelo destino: um cast não valida a faixa nem garante que toda informação seja preservada.

Truncamento em direção a zero

Diagrama comparando 3,8 convertido em 3 e -3,8 convertido em -3, com setas orientadas em direção a zero.

Converter para inteiro remove a fração; não escolhe o inteiro mais próximo.

Atenção

Precisão também importa

Os inteiros pequenos usados neste step podem ser representados exatamente em float. Não generalize isso para qualquer inteiro: números maiores podem não ter representação exata em ponto flutuante. Escolha o tipo conforme a faixa e a precisão necessárias para o cálculo.

Verifique sua decisão

Pratique o cast antes da divisão

Preencha a lacuna com o tipo que deve ser aplicado a total antes da divisão.

cpp
int total = 9;
int partes = 4;

float media = ()total / partes;

Truncar não é arredondar

Verdadeiro ou falso: supondo que o valor caiba em int, (int)-3.8f produz -4 porque o cast arredonda para baixo.

Passo 6 de 9

Amplie o tipo antes da operação

Corrija multiplicações de inteiros de 16 bits ampliando um operando antes do resultado intermediário.

Localize o resultado intermediário

O tipo do destino chega tarde

Em uma expressão, primeiro ocorre o cálculo; só depois o valor é atribuído à variável de destino. Portanto, declarar o destino como int32_t não altera automaticamente a faixa usada pela multiplicação.

No Uno R3, int tem 16 bits. Quando linhas e colunas são inteiros com sinal de 16 bits, a multiplicação entre eles precisa caber nessa faixa. Se o resultado matemático não couber, a expressão já tem estouro com sinal: não há resultado garantido pela linguagem, mesmo que o destino tenha 32 bits.

Onde a faixa precisa aumentar

Diagrama de dois fluxos: no primeiro, dois operandos de 16 bits entram em uma multiplicação de 16 bits antes de chegar a um destino de 32 bits; no segundo, um operando é ampliado para 32 bits antes da multiplicação.

A conversão deve acontecer antes da operação que pode exigir uma faixa maior.

Compare as posições da conversão

Amplie antes de multiplicar

Considere valores de 16 bits no Uno R3. Matematicamente, 300 × 200 vale 60000. Esse valor é maior que o máximo de int16_t, que é 32767, mas cabe tranquilamente em int32_t.

A primeira versão abaixo continua inadequada: o cast acontece depois de uma multiplicação que já precisava de mais faixa. Na segunda, o cast de um operando faz a multiplicação ocorrer com a faixa ampliada.

Cast depois e cast antes

Compare as duas posições possíveis para a conversão.

cpp
#include <stdint.h>

int16_t linhas = 300;
int16_t colunas = 200;

// Inadequado: a multiplicação em 16 bits já pode estourar.
int32_t areaInadequada = (int32_t)(linhas * colunas);

// Adequado para estes valores: um operando é ampliado antes.
int32_t area = (int32_t)linhas * colunas;

Dica

Regra prática

O cast não “conserta” um valor depois que a operação problemática ocorreu. Ele deve mudar o tipo de pelo menos um operando antes de *.

Ainda é preciso conferir a faixa: int32_t também tem limite. A conversão anterior apenas torna a multiplicação adequada quando o resultado matemático cabe no tipo ampliado e no destino.

Verifique a faixa, não só o destino

Confira cada etapa

Use a previsão matemática para decidir a faixa necessária antes de escrever o cálculo. Neste exemplo, não tente prever um valor produzido pela versão inadequada: com estouro de inteiro com sinal, o programa não tem resultado numérico garantido.

Conferência de faixa

Faixas e resultado matemático no exemplo

A versão correta é válida porque o resultado cabe tanto na operação ampliada quanto no destino.
EtapaTipo ou faixaValor relevante
Operandosint16_t300 e 200
Resultado matemático—60000
Multiplicação em 16 bitsmáx. 32767não comporta 60000
Multiplicação ampliadaint32_tcomporta 60000
Destinoint32_trecebe 60000

Teste sua análise

Pratique a posição correta

Complete o tipo da conversão para que linhas seja ampliado antes da multiplicação. Considere que linhas e colunas são int16_t e que o resultado precisa ser armazenado em int32_t.

Amplie um operando

Preencha a lacuna com o tipo de destino apropriado para ampliar o operando.

cpp
int32_t area = ()linhas * colunas;

Diagnóstico rápido

Por que esta declaração não protege, sozinha, a multiplicação?

int32_t resultado = linhas * colunas;

Passo 7 de 9

Diferencie retorno modular e estouro com sinal

Classifique cálculos próximos aos limites, distinguindo retorno modular sem sinal de estouro com sinal sem resultado garantido.

Entenda o retorno modular

Uma faixa circular para unsigned int

No Uno R3, unsigned int tem 16 bits: sua faixa vai de 0 a 65535. Quando uma operação efetiva nesse tipo passa de um limite, o resultado volta à faixa como se ela fosse circular: o cálculo é feito em módulo 2¹⁶, isto é, módulo 65536.

Assim, 65535U + 1U tem resultado matemático 65536, mas o resultado em unsigned int é 0. Da mesma forma, 0U - 1U resulta em 65535.

Isso é uma regra da aritmética sem sinal; não é um erro que o compilador precise impedir.

Retorno à faixa sem sinal

Diagrama de uma faixa de 0 a 65535 com setas circulares ligando o máximo ao zero e o zero ao máximo.

Em um unsigned int de 16 bits, somar ou subtrair além dos limites retorna à faixa de 0 a 65535.

Dica

Registre sempre duas coisas: o resultado matemático e o resultado conforme o tipo usado. Eles podem ser diferentes, mesmo quando a instrução compila normalmente.

Compare os dois resultados

Do cálculo matemático ao valor armazenado

Estes exemplos usam unsigned int de 16 bits, como no Uno R3. Os sufixos U deixam os literais sem sinal e tornam o tipo da operação explícito.

Compare os resultados

Exemplos de resultados em unsigned int de 16 bits

O resultado segundo o tipo é o valor que permanece dentro da faixa sem sinal.
ExpressãoResultado matemáticoResultado segundo o tipo
65535U + 1U655360
65534U + 3U655371
0U - 1U-165535
2U - 5U-365533

Atenção

Não trate esse retorno como uma maneira genérica de lidar com valores fora da faixa. Primeiro defina se o cálculo desejado realmente é modular; caso contrário, escolha uma faixa que comporte o resultado matemático.

Evite estouro com sinal

Com sinal, não suponha um retorno circular

Para inteiros com sinal, ultrapassar a faixa não produz um retorno modular garantido por C/C++. Por exemplo, no Uno R3, int tem faixa de -32768 a 32767. Portanto, 32767 + 1 não tem resultado garantido pela linguagem: é estouro com sinal, ou comportamento não definido.

Não conclua que o resultado será -32768 porque isso parece ocorrer em determinada placa ou teste. Uma compilação bem-sucedida e uma observação isolada não tornam o cálculo válido.

Dois comportamentos diferentes

Comparação entre uma faixa com sinal limitada, marcada com alerta ao ultrapassar o máximo, e uma faixa sem sinal circular.

Sem sinal há retorno modular definido; com sinal, ultrapassar a faixa não oferece resultado numérico garantido.

Dica

Quando o resultado matemático precisa ser maior que a faixa com sinal original, amplie um operando antes da operação. Converter apenas depois não recupera um resultado intermediário que já sofreu estouro.

Classifique e corrija

Classifique os casos de fronteira

Relacione cada caso ao resultado ou à classificação correta. Considere os tipos mostrados e não tente executar os casos com estouro para descobrir um suposto valor.

Toque em um item e depois no par correspondente.

Amplie antes de multiplicar

Complete o tipo da conversão para que a multiplicação não ocorra na faixa de int16_t.

cpp
#include <stdint.h>

int16_t amostras = 30000;
int16_t ganho = 2;

int32_t produto = ()amostras * ganho;

Passo 8 de 9

Evite perdas ao misturar ou reduzir tipos

Reconheça conversões implícitas que alteram valores negativos e evite reduzir resultados para destinos que não comportam sua faixa.

Não misture sinais sem conferir o efeito

Sinal diferente, significado diferente

No Uno R3, int e unsigned int têm 16 bits. Em uma expressão que mistura esses dois tipos, o operando int é convertido para unsigned int nesse caso. Se o valor com sinal for negativo, ele deixa de representar um número negativo e passa a representar um valor alto na faixa sem sinal.

Evite combinar tipos com e sem sinal quando o cálculo ou seu resultado pode ser negativo. Escolha tipos compatíveis com todos os valores que precisam fazer parte do domínio do cálculo.

O mesmo padrão pode ter outro significado

Diagrama que mostra um padrão de 16 bits interpretado como valor negativo com sinal e como valor alto sem sinal.

Com 16 bits, o padrão de -3 como int é interpretado como 65533 como unsigned int.

Mistura de int e unsigned int

Neste cálculo, ajuste é convertido implicitamente para unsigned int antes da soma.

cpp
int ajuste = -3;
unsigned int pulsos = 2;

unsigned int total = pulsos + ajuste;  // total recebe 65535 no Uno R3

Atenção

Não confunda retorno modular com ajuste negativo

Matematicamente, o cálculo pretendido seria 2 + (-3), isto é, -1. Mas, antes da soma, -3 passa a representar 65533 na faixa sem sinal de 16 bits. A expressão equivale a 2U + 65533U e resulta em 65535U.

O resultado não preserva um total negativo. Se valores negativos são válidos no problema, não use essa mistura de tipos para representá-los.

Separe cálculo válido de atribuição segura

Cálculo válido não garante armazenamento válido

Uma operação intermediária pode ser válida e, ainda assim, a atribuição seguinte pode perder informação. Verifique dois pontos separadamente:

  1. A faixa usada durante a operação comporta o resultado matemático?
  2. A faixa do destino comporta o valor que será armazenado?

No Uno R3, valores uint8_t são promovidos a int em uma soma, pois todos os valores de uint8_t cabem em int. Portanto, 200 + 100 é calculado como int e resulta validamente em 300. O risco abaixo está na redução posterior para uint8_t.

A perda ocorre na atribuição

A soma é válida como int, mas apenas uint16_t preserva o resultado 300 no destino.

cpp
#include <stdint.h>

uint8_t parteA = 200;
uint8_t parteB = 100;

int soma = parteA + parteB;       // 300: válido em int no Uno R3
uint16_t total = soma;            // preserva 300
uint8_t byte = soma;              // não preserva 300; recebe 44

Atenção

Um cast não valida a faixa

uint8_t comporta somente valores de 0 a 255. Ao converter 300 para esse tipo sem sinal de 8 bits, o valor é reduzido módulo 256 e se torna 44. Escrever (uint8_t)soma apenas deixa a conversão explícita: não aumenta a faixa do destino nem confirma que o valor cabe nele.

Dica

Escolha pelo intervalo necessário

Determine matematicamente os valores mínimo e máximo que precisam ser preservados. Para guardar 300 sem perda, escolha ao menos um tipo que comporte 300, como uint16_t.

Evite também depender da conversão de um valor que não cabe em um tipo inteiro com sinal. Não há um resultado que seja seguro assumir nesse caso; escolha antes um tipo compatível com a faixa necessária.

Amplie quando a operação realmente exigir

Amplie a operação e mantenha o resultado

Agora considere dois valores uint16_t que podem chegar a 40000. No Uno R3, uma soma desses valores pode precisar de mais de 16 bits: o máximo matemático é 80000.

Não basta declarar o destino como uint32_t: um operando deve ser convertido para uint32_t antes de +, para que a soma ocorra nessa faixa. O destino também precisa manter essa capacidade.

Pratique a faixa da operação e do destino

Preencha os dois tipos para que a soma de até 80000 seja calculada e armazenada sem perda.

Preserve uma soma de uint16_t

Complete os tipos que preservam a soma.

cpp
#include <stdint.h>

uint16_t amostraA = 40000;
uint16_t amostraB = 40000;

 total = ()amostraA + amostraB;

Verifique o significado antes da expressão

Analise a conversão implícita

Analise os tipos dos operandos antes de aceitar o resultado armazenado.

cpp
int saldo = -13;
unsigned int creditos = 10;

unsigned int resultado = saldo + creditos;

Confira o significado dos operandos

Qual é o principal problema desse cálculo?

Passo 9 de 9

Revise e valide um cálculo completo

Corrija um cálculo de tempo, teste valores de fronteira e aplique critérios para reconhecer resultados válidos, aproximações, perdas e operações inválidas.

1. Corrija antes de calcular

Defina o resultado e amplie no ponto certo

Você vai revisar um cálculo de tempo para um valor armazenado em uint16_t no Uno R3. O programa precisa produzir:

  • tempoUs: microssegundos inteiros, isto é, milissegundos × 1000;
  • tempoS: segundos com fração, isto é, milissegundos ÷ 1000.

Defina primeiro o resultado e a unidade desejados. Depois, confira o tipo em que cada operação ocorre. No Uno R3, uint16_t corresponde a unsigned int de 16 bits. Assim, sem uma ampliação prévia, tempoMs * 1000 ocorre como aritmética sem sinal de 16 bits.

Até 65 ms, o produto 65000 cabe nessa faixa. Em 66 ms, o resultado matemático 66000 ultrapassa 65535 e retorna modularmente para 464. Por isso, converta um operando para int32_t antes de multiplicar.

Dois caminhos para o mesmo tempo

Diagrama de um tempo em milissegundos seguindo dois caminhos: multiplicação com ampliação para gerar microssegundos e divisão em ponto flutuante para gerar segundos fracionários; um caminho sem ampliação retorna circularmente à faixa de 16 bits.

A conversão antes da multiplicação evita o retorno modular; um operando float torna a divisão fracionária.

Cálculo corrigido

Trecho completo para analisar. O cast faz a multiplicação ocorrer em int32_t; o literal 1000.0f faz a divisão ocorrer em ponto flutuante.

cpp
#include <stdint.h>

const uint16_t tempoMs = 500;

const int32_t tempoUs = (int32_t)tempoMs * 1000;
const float tempoS = (float)tempoMs / 1000.0f;

2. Registre o que acontece em cada faixa

Teste valores centrais e de fronteira

Teste um valor central e pontos de fronteira. A multiplicação corrigida ocorre em int32_t e, para qualquer valor possível de uint16_t, o produto por 1000 cabe em int32_t.

Os valores 65 e 66 revelam o limite da expressão original. Sem o cast, 65U * 1000U resulta em 65000, ainda dentro de unsigned int de 16 bits. Já 66U * 1000U tem previsão matemática de 66000, mas a aritmética sem sinal retorna módulo 65536: o resultado passa a ser 464.

Na coluna de segundos, ≈ indica uma aproximação de float. A divisão não sofre truncamento inteiro, mas várias frações decimais não possuem representação binária exata.

Conferência do cálculo

Casos para tempoUs = (int32_t)tempoMs * 1000 e tempoS = (float)tempoMs / 1000.0f.

O cálculo corrigido preserva os microssegundos; a expressão original retorna modularmente a partir de 66 ms.
tempoMsPrevisão matemáticaIntermediário corrigidoResultado segundo os tiposPerda ou observação
00 µs; 0 sint32_t; float0; 0,0Não
6565000 µs; 0,065 sint32_t; float65000; ≈ 0,065Original ainda dá 65000
6666000 µs; 0,066 sint32_t; float66000; ≈ 0,066Original sem cast retorna 464
500500000 µs; 0,5 sint32_t; float500000; 0,5Não nesta fração
6553465534000 µs; 65,534 sint32_t; float65534000; ≈ 65,534Sem truncamento; float aprox.
6553565535000 µs; 65,535 sint32_t; float65535000; ≈ 65,535Sem truncamento; float aprox.

Dica

Leia cada operação antes do destino

Não escreva ((int32_t)(tempoMs * 1000)). Nessa versão, a multiplicação ocorre antes do cast como unsigned int de 16 bits e pode retornar modularmente. Também não substitua 1000.0f por 1000 quando precisar da parte fracionária: isso faria a divisão ocorrer entre inteiros.

3. Complete o cálculo seguro

Aplique as duas correções

Complete as expressões para que 66 ms gere 66000 µs e aproximadamente 0,066 s, sem retorno modular nem truncamento inteiro.

cpp
const uint16_t tempoMs = 66;

const int32_t tempoUs = ()tempoMs * 1000;
const float tempoS = (float)tempoMs / ;

4. Valide e encerre

Classifique antes de confiar no resultado

Relacione cada situação ao diagnóstico correto. Considere unsigned int de 16 bits no Uno R3.

Toque em um item e depois no par correspondente.

Justifique a posição da conversão

Explique por que a conversão para int32_t deve ficar antes de tempoMs * 1000, considerando o caso de 66 ms.

Escreva pelo menos 80 caracteres (0/80).

Resumo

Síntese final

Use esta sequência ao escrever ou revisar um cálculo numérico:

  • Defina o resultado desejado e suas unidades.
  • Use parênteses para deixar claro o agrupamento necessário.
  • Verifique divisor, sinal e faixa dos operandos e do resultado intermediário.
  • Converta um operando antes da operação que precisa de maior faixa.
  • Use float antes da divisão quando a fração for necessária.
  • Confira se o destino também comporta o resultado.
  • Teste zero, um valor central e valores próximos aos limites.

Tutorial concluído

Parabéns! Você concluiu: Cálculos e conversões numéricas em C/C++

Muito bem! Agora você consegue analisar a ordem, a faixa e as conversões de uma expressão antes de confiar no resultado.

Baixe o Aplicativo agora para ter acesso a + de 5000 cursos gratuitos, exercícios, certificado e muito conteúdo sem pagar nada!

  • Cursos online 100% gratuitos do início ao fim

    Milhares de cursos online em vídeo, ebooks e áudiobooks.

  • Mais de 60 mil exercícios gratuitos

    Para testar seus conhecimentos no decorrer dos cursos online

  • Certificado Digital gratuito válido em todo o Brasil

    Gerado diretamente na galeria de fotos do seu celular e enviado ao seu e-mail

Aplicativo Cursa na tela de ebook, na tela de curso em vídeo e na tela de exercícios do curso, mais o certificado de conclusão de curso