C# Conversão de Tipos
Nas aulas anteriores, o compilador reclamou várias vezes: "não é possível converter implicitamente tipo double em int". Nesta aula você vai entender essa regra. Algumas conversões o C# faz sozinho, porque nunca perdem nada. Outras você precisa pedir, com um cast, porque podem perder dados. Saber a diferença evita médias erradas, centavos sumidos e números que viram outros sem aviso.
- As conversões implícitas, que o C# faz sozinho (
intparalong,double,decimal). - O cast explícito,
(int)valor, e o que ele faz com a parte decimal (corta, não arredonda). - A perda de dados:
(byte)300dá44, e como ocheckedtransforma isso emOverflowException. - Cast ×
Math.Round×Convert.ToInt32. - A promoção nas contas: por que
soma / quantidadedá 8 e(double)soma / quantidadedá 8,33. - Por que texto não vira número com cast (
CS0030).
Todos os programas desta aula foram rodados com o .NET 10 (C# 14) na máquina fictícia da Ana (usuária ana, computador notebook), na pasta ~/cs, com dotnet run arquivo.cs.
A ideia: o copo e a jarra
Despejar um copo d'água numa jarra grande é sempre seguro: cabe tudo, não se perde nada. Já despejar a jarra cheia num copo pode derramar. Se alguém pedir isso, você vai querer ter certeza de que a pessoa sabe o que está fazendo.
O C# pensa igual:
- de um tipo "menor" para um "maior" (
intparalong), a conversão é implícita: o C# faz sozinho, sem você escrever nada; - de um tipo "maior" para um "menor" (
doubleparaint), pode derramar, então a conversão tem de ser explícita: você escreve o tipo de destino entre parênteses, e isso se chama cast (em inglês, "molde").
1. Conversões implícitas: o C# faz sozinho
int pontos = 1500; long total = pontos; double media = pontos; decimal saldo = pontos; char letra = 'A'; int codigo = letra; float f = 2.5f; double d = f; Console.WriteLine(total); Console.WriteLine(media); Console.WriteLine(saldo); Console.WriteLine(codigo); Console.WriteLine(d);
$ dotnet run implicita.cs 1500 1500 1500 65 2,5
- long total = pontos;
- Todo
intcabe numlong. Nenhuma palavra extra. - double media = pontos; · decimal saldo = pontos;
- Um inteiro também entra num
doubleou numdecimal:1500vira1500,0, que aparece como1500. - int codigo = letra;
- Um
charentra numint: vale o código do caractere,65(aula char e bool). - double d = f;
floatcabe emdouble.
O caminho das conversões implícitas entre os números mais usados:
| De | Vira sozinho em |
|---|---|
byte | short, ushort, int, uint, long, ulong, float, double, decimal |
short | int, long, float, double, decimal |
char | ushort, int, uint, long, ulong, float, double, decimal |
int | long, float, double, decimal |
long | float, double, decimal |
float | double |
double | nenhum |
decimal | nenhum |
Repare nas duas últimas linhas: double e decimal não viram nada sozinhos, nem um no outro. O double tem um alcance enorme (até 10308) e o decimal tem mais dígitos exatos; nenhum dos dois "cabe" inteiro no outro.
A exceção: long para double perde dígitos
Há um caso em que a conversão implícita perde algo: inteiros muito grandes num float ou double. O valor continua perto, mas os últimos dígitos se perdem:
long grande = 123456789012345678; float f = grande; double d = grande; Console.WriteLine(grande); Console.WriteLine(f); Console.WriteLine(d); Console.WriteLine((long)d);
$ dotnet run longfloat.cs 123456789012345678 1,2345679E+17 1,2345678901234568E+17 123456789012345680 « voltou diferente: …678 virou …680
O C# deixa porque o tamanho do número se mantém (só a precisão cai), e isso raramente importa. Mas é por isso que códigos, CPFs e números de cartão nunca devem ir para double.
2. Conversão explícita: o cast
Quando a conversão pode perder dados, o C# exige que você peça, escrevendo o tipo de destino entre parênteses, na frente do valor:
double nota = 7.9; int inteiro = (int)nota; Console.WriteLine(inteiro); Console.WriteLine((int)-7.9); long grande = 1000; int menor = (int)grande; Console.WriteLine(menor); decimal preco = 19.99m; int reais = (int)preco; Console.WriteLine(reais); double d = (double)preco; Console.WriteLine(d); decimal volta = (decimal)0.1; Console.WriteLine(volta);
$ dotnet run explicita.cs 7 « 7,9 virou 7: cortou, não arredondou -7 1000 19 19,99 0,1
- (int)nota
- O cast: "converta
notaparaint". Dedoublepara inteiro, a parte depois da vírgula é jogada fora: 7,9 vira 7, e -7,9 vira -7 (o corte é sempre em direção ao zero). - (int)grande
- De
longparaint. 1000 cabe, então nada se perde. O compilador não olha o valor; olha os tipos, e umlongpoderia não caber. - (int)preco
- De
decimalparaint: também corta,19. - (double)preco · (decimal)0.1
- Entre
decimaledouble, sempre com cast, nas duas direções.
Sem o cast, cada uma dessas linhas daria o erro que você já conhece:
long visitas = 1000; int v = visitas; Console.WriteLine(v);
$ dotnet run conv_erro_long.cs /home/ana/cs/conv_erro_long.cs(2,9): error CS0266: Não é possível converter implicitamente tipo "long" em "int". Existe uma conversão explícita (há uma conversão ausente?) Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
Agora a mensagem faz sentido: "existe uma conversão explícita" = existe um cast; "há uma conversão ausente?" = "você esqueceu de escrever o cast?". Em inglês: Cannot implicitly convert type 'long' to 'int'. An explicit conversion exists (are you missing a cast?).
3. Quando o cast derrama
O cast é uma promessa: "eu sei que cabe". Se não couber, o C# não reclama. Ele faz o que der:
int grande = 300; byte pequeno = (byte)grande; Console.WriteLine(pequeno); long enorme = 5_000_000_000; int corte = (int)enorme; Console.WriteLine(corte); int negativo = -1; uint semSinal = (uint)negativo; Console.WriteLine(semSinal);
$ dotnet run perda.cs 44 « 300 num byte 705032704 « 5 bilhões num int 4294967295 « -1 num uint
Entre inteiros, o cast guarda só os bits que cabem e joga o resto fora. O byte tem 8 bits (0 a 255): 300 - 256 = 44. É o mesmo "dar a volta" do estouro (aula Inteiros). Nenhum erro, nenhum aviso: o programa segue com um número que não tem nada a ver com o original.
checked também vale para o cast
O mesmo checked da aula de inteiros transforma o "derramou" em erro:
int grande = 300; byte pequeno = checked((byte)grande); Console.WriteLine(pequeno);
$ dotnet run perdachecked.cs Unhandled exception. System.OverflowException: Arithmetic operation resulted in an overflow. at Program.<Main>$(String[] args) in /home/ana/cs/perdachecked.cs:line 2
Com decimal, a conferência é sempre ligada, mesmo sem checked: (int) de um decimal grande demais lança System.OverflowException: Value was either too large or too small for an Int32. ("o valor era grande ou pequeno demais para um Int32").
Valores fixos: o compilador confere
Se o valor é fixo no código, o compilador vê que não cabe e não compila. Um programa com Console.WriteLine((int)1e20); dá:
$ dotnet run doubleint.cs /home/ana/cs/doubleint.cs(1,19): error CS0221: O valor de constante "1E+20" não pode ser convertido em "int" (use a sintaxe "unchecked" para substituir) Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
double fora da faixa não tem resultado confiável
Com variáveis, o .NET 10 do laboratório converte (int) de 1e20 em 2147483647 (o maior int) e de double.NaN em 0. O editor do guia mostra o mesmo. Mas esse comportamento já mudou entre versões do .NET (até o .NET 8 o resultado dependia do processador: num PC comum, Intel ou AMD, dava -2147483648 nos dois casos). Não escreva programas que dependam dele: confira a faixa antes, ou use checked.
4. Cortar ou arredondar?
O cast corta. Quando você quer arredondar, há outras ferramentas:
double nota = 7.9; Console.WriteLine((int)nota); Console.WriteLine((int)Math.Round(nota)); Console.WriteLine(Convert.ToInt32(nota)); Console.WriteLine(Convert.ToInt32(2.5)); Console.WriteLine(Convert.ToInt32(3.5)); Console.WriteLine(Math.Round(2.5)); Console.WriteLine(Math.Round(2.5, MidpointRounding.AwayFromZero)); Console.WriteLine((int)Math.Floor(7.9)); Console.WriteLine((int)Math.Ceiling(7.1));
$ dotnet run arredondar.cs 7 8 8 2 « 2,5 virou 2! 4 « 3,5 virou 4 2 3 7 8
- (int)nota → 7
- Corta a parte decimal.
- (int)Math.Round(nota) → 8
Math.Round("arredondar") devolve umdoublearredondado (8), e o cast o transforma emintsem perder nada.- Convert.ToInt32(nota) → 8
- A classe
Convert("converter") arredonda em vez de cortar. - Convert.ToInt32(2.5) → 2 · Convert.ToInt32(3.5) → 4
- A surpresa: quando o número está exatamente no meio (x,5), o .NET arredonda para o par mais próximo. É o "arredondamento do banqueiro", que evita que os arredondamentos puxem sempre para cima.
Math.Roundfaz o mesmo. - MidpointRounding.AwayFromZero → 3
- Para o arredondamento da escola (x,5 sobe), peça
AwayFromZero, "para longe do zero". - Math.Floor · Math.Ceiling
- "Piso" (arredonda sempre para baixo) e "teto" (sempre para cima). Detalhes em Matemática.
5. Conversões dentro das contas (promoção)
Quando uma conta mistura tipos, o C# converte o menor para o maior antes de calcular. Isso se chama promoção. E quando a conta tem só inteiros, ela é feita em inteiros, mesmo que o resultado vá para um double:
int soma = 7 + 8 + 10;
int quantidade = 3;
double media = soma / quantidade;
Console.WriteLine($"Média: {media}");
$ dotnet run notas.cs Média: 8 « 25 / 3 devia dar 8,33…
soma / quantidade é int dividido por int: o resultado é o int 8 (a divisão inteira joga fora o resto, aula Operadores Aritméticos). Só depois o 8 é convertido para double. O conserto é converter um dos lados antes da divisão:
int a = 7; int b = 2; Console.WriteLine(a / b); Console.WriteLine((double)a / b); Console.WriteLine((double)(a / b)); Console.WriteLine(a / 2.0);
$ dotnet run promocao.cs 3 3,5 3 3,5
- a / b → 3
- Divisão inteira.
- (double)a / b → 3,5
- O cast vale só para o
a, que vira7,0. Agora édoubledividido porint; obé promovido adouble, e a divisão é com vírgula. - (double)(a / b) → 3
- Aqui os parênteses fazem a divisão inteira primeiro (3) e só depois convertem:
3,0. Tarde demais. - a / 2.0 → 3,5
- Com um número fixo, basta escrevê-lo com ponto:
2.0édouble.
Missão: a média das notas 7, 8 e 10 sai Média: 8,00. Use um cast (double) em soma, antes da divisão, para mostrar Média: 8,33. (O :F2 dentro das chaves mostra o número com 2 casas; aula Formatar Números.)
int soma = 7 + 8 + 10;
int quantidade = 3;
double media = soma / quantidade;
Console.WriteLine($"Média: {media:F2}");
int soma = 7 + 8 + 10;
int quantidade = 3;
double media = (double)soma / quantidade;
Console.WriteLine($"Média: {media:F2}");
O tipo do resultado
| Conta | Resultado | Por quê |
|---|---|---|
int + long | long | O int é promovido a long |
int + double | double | O int é promovido a double |
int * decimal | decimal | O int é promovido a decimal |
char + int | int | O char vira o seu código |
short + short, byte + byte | int | Contas com tipos menores que int são sempre feitas em int |
decimal * double | não compila (CS0019) | Não há promoção entre os dois |
Todos esses tipos foram conferidos com GetType() no laboratório.
6. Texto não vira número com cast
O cast só converte entre tipos "parentes", como os números entre si. Uma string não é parente de int:
string texto = "42"; int numero = (int)texto; Console.WriteLine(numero);
$ dotnet run castcomstring.cs /home/ana/cs/castcomstring.cs(2,14): error CS0030: Não é possível converter tipo "string" em "int" Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
Repare na diferença para o CS0266: aqui a mensagem não diz "implicitamente" nem "existe uma conversão explícita". O CS0030 quer dizer: não existe conversão nenhuma, nem com cast. O mesmo acontece com (bool)5 e (int)true. Para transformar texto em número existe int.Parse e int.TryParse, a próxima aula: Texto → Número.
O caminho contrário, de número para texto, é fácil: todo valor tem o método ToString():
int idade = 29; string texto = idade.ToString(); Console.WriteLine(texto + "!"); double altura = 1.65; Console.WriteLine(altura.ToString());
$ dotnet run convtostring.cs 29! 1,65
O ToString() usa a cultura do computador: 1,65 no Ubuntu da Ana. A aula Cultura mostra como escolher outra.
7. A classe Convert
Além do cast, o .NET tem a classe Convert, com um método para cada tipo de destino: Convert.ToInt32, Convert.ToDouble, Convert.ToBoolean, Convert.ToString… Ela é mais "esperta" que o cast em alguns pontos:
Console.WriteLine(Convert.ToInt32(true)); Console.WriteLine(Convert.ToBoolean(0)); Console.WriteLine(Convert.ToDouble(10)); Console.WriteLine(Convert.ToInt32(300.6)); Console.WriteLine(Convert.ToByte(300));
$ dotnet run convert1.cs 1 False 10 301 Unhandled exception. System.OverflowException: Value was either too large or too small for an unsigned byte. at System.Convert.ThrowByteOverflowException() at System.Convert.ToByte(Int32 value) at Program.<Main>$(String[] args) in /home/ana/cs/convert1.cs:line 5
- Converte
boolem número (true= 1) e número embool(0 =false), o que o cast não faz. - Arredonda em vez de cortar (
300,6virou301). - Confere sempre a faixa:
Convert.ToByte(300)lançaOverflowExceptionem vez de dar 44.
| Quero… | Use |
|---|---|
| Guardar num tipo maior | Nada: a conversão é implícita |
| Jogar fora a parte decimal | (int)valor |
| Arredondar para inteiro | (int)Math.Round(valor) ou Convert.ToInt32(valor) |
Converter entre decimal e double | (decimal)d, (double)m |
| Converter para um tipo menor, com erro se não couber | checked((byte)x) ou Convert.ToByte(x) |
| Fazer uma divisão com vírgula entre inteiros | (double)a / b |
| Número para texto | valor.ToString() |
| Texto para número | int.Parse / int.TryParse (aula Texto → Número) |
Quando dá errado
error CS0266: Não é possível converter implicitamente tipo "…" em "…". Existe uma conversão explícita (há uma conversão ausente?)
O que significa: a conversão pode perder dados (double para int, long para int, double para decimal), então o C# não a faz sozinho.
Como consertar: se você sabe que é seguro (ou quer mesmo cortar), escreva o cast: (int)valor. Se não, mude o tipo da variável de destino para um que caiba.
error CS0030: Não é possível converter tipo "string" em "int"
O que significa: não existe conversão entre os dois tipos, nem com cast.
Como consertar: de texto para número, int.Parse ou int.TryParse; de bool para número, ativo ? 1 : 0 ou Convert.ToInt32(ativo).
error CS0221: O valor de constante "1E+20" não pode ser convertido em "int" (use a sintaxe "unchecked" para substituir)
O que significa: um cast de um valor fixo que não cabe no tipo de destino.
Como consertar: use um tipo que caiba. O unchecked( … ) que a mensagem sugere só serve se você quer mesmo o valor "derramado".
O que significa: a divisão foi feita entre dois inteiros, antes de o resultado ir para o double.
Como consertar: (double)soma / quantidade, com o cast dentro da conta, num dos lados. (double)(soma / quantidade) não resolve.
44, 705032704, 4294967295)
O que significa: um cast de inteiro para um tipo menor (ou sem sinal) em que o valor não cabia.
Como consertar: use checked((byte)x) ou Convert.ToByte(x) para ter OverflowException, ou um tipo maior.
System.OverflowException: Value was either too large or too small for an Int32.
O que significa: uma conversão conferida (de decimal, com Convert ou dentro de checked) recebeu um valor fora da faixa do tipo.
Como consertar: confira o valor antes, ou use um tipo maior (long).
Referência rápida
| Escrita | O que faz | Exemplo |
|---|---|---|
long l = i; | Implícita (do menor para o maior) | int → long, double, decimal |
(int)x | Cast explícito; com vírgula, corta | (int)7.9 = 7, (int)-7.9 = -7 |
(byte)300 (com variável) | Inteiros: guarda os bits que cabem | 44 |
checked((byte)x) | Cast conferido | OverflowException |
(double)a / b | Divisão com vírgula entre inteiros | 7 / 2 = 3; (double)7 / 2 = 3,5 |
Math.Round(x) | Arredonda (x,5 vai para o par) | Math.Round(2.5) = 2 |
Math.Round(x, MidpointRounding.AwayFromZero) | Arredonda como na escola | 2,5 → 3 |
Convert.ToInt32(x) | Arredonda e confere a faixa | Convert.ToInt32(7.9) = 8 |
x.ToString() | Número para texto | "1,65" |
Exercícios
1. O cast
Complete para converter o double em int:
2. Cortar
Quanto vale (int)9.99?
3. Derramou
Com int x = 260;, quanto vale (byte)x?
4. Qual linha compila sem cast?
5. Com int a = 9, b = 2;, qual expressão vale 4,5?
6. int n = (int)"15"; O que acontece?
7. O que Convert.ToInt32(4.5) devolve?
Missão final: reais e centavos
Missão: o programa quer separar o preço R$ 19,99 em reais e centavos, mas não compila: um decimal não entra sozinho num int (CS0266). Use o cast (int) nas duas linhas (na segunda, em volta da conta inteira, com parênteses) para mostrar 19 reais e 99 centavos.
decimal preco = 19.99m;
int reais = preco;
int centavos = (preco - reais) * 100;
Console.WriteLine($"{reais} reais e {centavos} centavos");
decimal preco = 19.99m;
int reais = (int)preco;
int centavos = (int)((preco - reais) * 100);
Console.WriteLine($"{reais} reais e {centavos} centavos");
Na solução, (int)preco corta para 19; preco - reais é 0,99 (um decimal, exato); vezes 100 dá 99,00; e o (int) de fora faz dele o inteiro 99. Com double no lugar de decimal, a mesma conta dá 98 centavos no laboratório: 19,99 não é exato em double, a diferença vira 0,98999…, e o cast corta. Mais um motivo para dinheiro ser decimal.
Resumo
- Implícita: do tipo menor para o maior, o C# converte sozinho (
int→long,double,decimal;char→int). - Explícita (cast):
(int)x, quando pode perder dados. Sem o cast:CS0266. - De número com vírgula para inteiro, o cast corta:
(int)7.9= 7. Para arredondar:Math.RoundouConvert.ToInt32(x,5 vai para o par). - Entre inteiros, um cast que não cabe dá a volta sem aviso (
(byte)300= 44).checkedeConvertlançamOverflowException. - Contas com tipos misturados promovem o menor; só com inteiros, a divisão é inteira. Use
(double)soma / quantidade. - Texto e número não se convertem com cast (
CS0030): número → texto comToString(), texto → número comParse/TryParse.