CodeGym /Cursos /C++ SELF /Conversoes implicitas: misturando int e double

Conversoes implicitas: misturando int e double

C++ SELF
Nível 6, Lição 4
Disponível

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
7 / 2
int e int
int
3
7 / 2.0
int e double
double
3.5
2 + 0.5
int e double
double
2.5
double x = 7 / 2;
a direita int/int a direita
int
, a esquerda
double
3.0
int x = 7 / 2.0;
a direita contem double a direita
double
, a esquerda
int
3

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.

1
Tarefa
C++ SELF, nível 6, lição 4
Bloqueado
Ticket médio
Ticket médio
1
Tarefa
C++ SELF, nível 6, lição 4
Bloqueado
Entrega via flag
Entrega via flag
1
Tarefa
C++ SELF, nível 6, lição 4
Bloqueado
Próxima letra
Próxima letra
1
Tarefa
C++ SELF, nível 6, lição 4
Bloqueado
Parte inteira e fracionária
Parte inteira e fracionária
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION