1. Tipo interno do calculo da expressao
Se voce esta começando, e facil pensar assim: “Numero e numero. Qual a diferenca, int ou double — tanto faz, eh 7”. Na pratica a diferenca e enorme: o tipo influencia como a expressao e calculada, quais valores sao possiveis, e o que sera perdido ao gravar o resultado.
Conversoes implicitas (implicit conversions) sao as regras pelas quais o compilador automaticamente converte um tipo para outro para que a operacao faca sentido. E como um tradutor automatico no mensageiro: util, ate ele "corrigir" sua ideia para algo estranho. Entao nosso objetivo hoje e aprender a prever quando o compilador "tranlata" os tipos, em que direcao, e onde disso surgem bugs.
Quando voce escreve:
double x = 5 / 2;
a pessoa iniciante costuma imaginar: “a esquerda double, entao a divisao sera fracionaria”. Mas o compilador pensa diferente: ele calcula o que esta a direita segundo as regras de tipos da direita, obtém o resultado, e so entao tenta gravar isso na variavel da esquerda (e a gravacao e uma conversao separada).
Para nao se confundir, e util ter em mente esta esteira:
flowchart TD
A[Operandos da expressao] --> B[Promotions: tipos pequenos "sao elevados"]
B --> C[Conversao aritmetica comum: e escolhido um tipo comum]
C --> D[Calculo da operacao]
D --> E[Atribuicao: o resultado e convertido para o tipo da variavel a esquerda]
E aqui um ponto psicologico importante: o tipo da variavel a esquerda nao "manda" no calculo da direita. Ele influencia apenas a gravacao final do resultado.
3. Promotions: elevando tipos pequenos
Promotion e a "elevacao" automatica de um tipo para outro mais apropriado/padrao para calculos. O caso mais comum, que voce ja viu mesmo sem perceber: char na aritmetica se comporta como numero. Nao e magia nem traicao, isso e promotion.
No padrao C++ a ideia de integral promotions (promocoes integrais) existe como uma parte importante das regras; ate em revisoes editoriais de rascunhos de trabalho ve-se que formulacoes e termos relacionados a promotions sao discutidos cuidadosamente.
Exemplo: char vira numero
Faca um pequeno experimento:
#include <iostream>
int main() {
char c = 'A';
int next = c + 1;
std::cout << next << '\n'; // 66 (se 'A' = 65 no ASCII)
}
Voce poderia esperar "B", mas obteve um numero. Por que? Porque a expressao c + 1 e aritmetica, e para aritmetica char normalmente e promovido (elevado) para int, e o resultado tambem se torna um inteiro.
Se quiser exatamente um caractere — e preciso pensar explicitamente no que se esta fazendo; conversoes explicitas deixaremos para niveis futuros do curso.
Exemplo: bool na aritmetica — tambem e numero
Parece piada, mas em calculos true muitas vezes se comporta como 1, e false como 0.
#include <iostream>
int main() {
bool paid = true;
int total = 100 + paid;
std::cout << total << '\n'; // 101
}
Funciona, mas normalmente nao e uma boa ideia para legibilidade. O compilador lida internamente, mas a pessoa que le o codigo (incluindo voce daqui a uma semana) pode ficar confusa.
Usual arithmetic conversions: como se escolhe o tipo comum
Quando uma operacao recebe dois numeros de tipos diferentes, o compilador precisa decidir: “Em que tipo executar a operacao?” Esse conjunto de regras e frequentemente chamado de usual arithmetic conversions. Em materiais sobre rascunhos de trabalho eles ate destacaram adicionar cruzamentos de referencia para esse termo, ou seja, o tema e realmente fundamental.
Para nosso nivel atual basta memorizar a regra pratica:
Se na expressao participa um double, quase sempre os calculos sobem para double, para nao perder a parte fracionaria.
Ou seja:
- int + int → int
- int + double → double
- int / int → int (mesmo se voce depois colocar isso em double)
- double / int → double
4. Misturando int e double
Armacao nº1: "coloquei em double, entao sera fracionario" — nao
Aqui vai uma demonstracao que confunde quase todo mundo na primeira vez (e isso e normal):
#include <iostream>
int main() {
int sum = 5;
int count = 2;
double avg = sum / count; // ambos int -> divisao int
std::cout << avg << '\n'; // 2
}
Por que 2, e nao 2.5? Porque sum / count e calculado como int / int, ou seja, o resultado e inteiro, a parte fracionaria e descartada. Depois esse inteiro e simplesmente armazenado em double como 2.0. Sem erro, apenas "nao o que voce esperava".
Dica nº1: "como forcar a divisao a ser fracionaria" — adicione double na expressao
O jeito mais simples no nosso nivel (sem cast explicito) e garantir que ao menos um operando seja double. Por exemplo, escrever 2.0 em vez de 2.
#include <iostream>
int main() {
int sum = 5;
int count = 2;
double avg = sum / 2.0; // agora a expressao e em double
std::cout << avg << '\n'; // 2.5
}
Aqui o compilador ve int / double, converte int para double, calcula em double e obtém resultado fracionario.
Armacao nº2: "perda silenciosa de dados" ao converter double → int
Quando voce atribui um numero fracionario a uma variavel inteira, a parte fracionaria simplesmente desaparece:
#include <iostream>
int main() {
double price = 19.99;
int euros = price;
std::cout << euros << '\n'; // 19
}
Isso nao e arredondamento, e exatamente truncamento da parte fracionaria. O compilador nao e obrigado a te salvar. Ele age assim: "Voce escreveu assim — eu fiz assim".
Mini-tabela de referencia
As vezes e util ter um pequeno "mapa" para nao adivinhar.
| Expressao | Tipos dos operandos | Tipo do calculo | Exemplo de resultado |
|---|---|---|---|
|
int e int | |
|
|
int e double | |
|
|
int e double | |
|
|
a direita int/int | a direita , a esquerda |
|
|
a direita contem double | a direita , a esquerda |
|
Aqui esta o principal: primeiro se calcula a direita, depois se ajusta ao tipo da esquerda.
5. Mini-calculadora de checkout: aplicando o tema no app
Para nao deixar o tema no vazio, vamos continuar com o aplicativo condicional que desenvolvemos ao longo do curso: um "mini-terminal do caixa" para o console. Nao pretende ser contabilidade real (dinheiro real e melhor nao guardar em double, mas isso e outra historia mais adulta), porem mostra bem erros tipicos de iniciantes.
Primeiro passo: calculamos preco medio e detectamos o bug
Lemos a quantidade de itens e o total. Quantidade e inteira, total e fracionario.
#include <iostream>
int main() {
int items = 0;
double total = 0.0;
std::cin >> items >> total;
double avg = total / items;
std::cout << avg << '\n'; // por exemplo: 12.5
}
Aqui esta tudo bem porque total e double, entao a divisao ocorre em double.
E agora vamos mostrar a versao "ruim" — exatamente aquela que e comumente escrita por habito:
#include <iostream>
int main() {
int items = 0;
int total = 0;
std::cin >> items >> total;
double avg = total / items;
std::cout << avg << '\n'; // se total=25 items=2 -> 12
}
Se total = 25, items = 2, a "media verdadeira" e 12.5, mas voce recebera 12. E isso nao porque double avg e ruim — e porque total / items foi calculado como divisao inteira.
Correcao sem casts: torne um operando double
Se em sentido a soma pode ser fracionaria — e mais honesto armazenar em double. Mas mesmo que a soma seja inteira (por exemplo, "euros sem centavos"), voce ainda pode querer obter uma media fracionaria. Entao basta "introduzir" double na expressao:
#include <iostream>
int main() {
int items = 0;
int total = 0;
std::cin >> items >> total;
double avg = total / (items * 1.0);
std::cout << avg << '\n'; // se total=25 items=2 -> 12.5
}
Observe: (items * 1.0) se torna double, entao total / double tambem se torna double. Sim, parece um pouco "esperto", mas nesta fase e uma tecnica normal enquanto nao aprendemos casts explicitos.
Adicionando desconto em porcentagem: novamente misturando int e double
O desconto muitas vezes e informado como inteiro em porcentagem, enquanto o total final e fracionario.
#include <iostream>
int main() {
double total = 0.0;
int discountPercent = 0;
std::cin >> total >> discountPercent;
double discount = total * discountPercent / 100.0;
double finalTotal = total - discount;
std::cout << finalTotal << '\n'; // por exemplo: 899.1
}
Aqui e importante que / 100.0 — divisao em double. Se fosse / 100, entao total * discountPercent ja seria double, e a divisao igualmente seria double / int, ou seja double. Mas 100.0 deixa a intencao bem clara: estamos trabalhando com fracoes.
6. Como pensar sobre tipos em formulas
Hábito muito util: quando voce escreve uma formula, mentalmente faca duas perguntas.
Primeira pergunta: "Onde nesta formula deve aparecer a parte fracionaria?" Se a resposta for "deve", entao verifique se ha pelo menos um double na expressao. Se nao houver — quase certamente voce esta fazendo aritmetica inteira por acidente.
Segunda pergunta: "Onde eu vou gravar o resultado?" Se voce grava em int, entao deve estar preparado moralmente que a parte fracionaria desaparecera. As vezes isso e ok (por exemplo, voce armazena "numero inteiro de pessoas"), outras vezes e bug (por exemplo, voce armazena preco).
7. Erros tipicos
Erro nº1: esperar resultado fracionario de int / int porque a esquerda esta um double.
Este e um dos truques logicos mais comuns. A divisao e definida pelos tipos dos operandos na expressao da direita, nao pelo tipo da variavel da esquerda. Portanto double x = 5 / 2; da 2, nao 2.5. Resolve-se com o habito: se voce espera fracao — assegure que um double esteja presente diretamente na expressao.
Erro nº2: "consertar" o problema com parenteses, quando o problema e de tipos.
Parenteses mudam a ordem de calculo, mas nao mudam os tipos dos operandos. Voce pode colocar parenteses e ainda obter resultado incorreto por causa da divisao inteira. Em tais situacoes e preciso mudar os tipos de dados ou ao menos um literal (por exemplo, 2.0 em vez de 2).
Erro nº3: perder silenciosamente a parte fracionaria ao atribuir double para int.
int a = 19.99; vira 19 sem avisos a nivel logico. O compilador considera isso uma conversao permissivel. Se a parte fracionaria e importante — mantenha o valor em double. Se nao for, e melhor escrever o calculo de modo que isso fique evidente no codigo (por exemplo, arredondar/truncar conscientemente — isso discutiremos mais tarde, quando tivermos ferramentas adicionais).
Erro nº4: usar char e bool em aritmetica "por acidente".
char e bool facilmente aparecem em expressoes: somou uma flag, contou um caractere como numero, e de repente surge um resultado misterioso. Formalmente isso e explicado por promotions, mas na pratica se resolve com disciplina: a aritmetica deve ser feita sobre valores que voce realmente considera numeros no contexto do problema, nao sobre tipos "para texto" ou "para logica".
Erro nº5: escrever formulas de modo que o leitor nao saiba em que tipo o calculo esta sendo feito.
Mesmo que o compilador calcule "como voce queria", a pessoa pode interpretar diferente. Entao as vezes e melhor usar 100.0 em vez de 100, ou armazenar o total ja em double, para que a formula seja lida sem ambiguidade. Nao e sobre "estetica", e sobre reduzir bugs futuros.
GO TO FULL VERSION