C# Tipos e Limites
Cada tipo de número do C# ocupa um tamanho fixo na memória, e por isso tem um menor e um maior valor. Esta página junta, em tabelas, o tamanho e os limites de cada tipo, a precisão dos números com vírgula, o tipo de cada literal, o valor padrão e o que acontece quando uma conta passa do limite. Para aprender com calma, veja Tipos de Dados, Inteiros e float, double e decimal.
Todos os valores foram impressos de verdade pelos programas desta página, no dotnet 10.0.112 da máquina da Ana (pasta ~/cs/ref, cultura pt-BR, por isso a vírgula decimal) e conferidos no editor do guia. Quando os dois diferem, a página diz.
Atalhos: Tamanhos e limites · Precisão (float, double, decimal) · Valores especiais · Outros tipos · O tipo de cada literal · Valor padrão · Estouro e conversões · Quando dá errado · Experimente
Tamanhos e limites
Todo tipo numérico tem as constantes MinValue (o menor valor) e MaxValue (o maior). O sizeof diz quantos bytes ele ocupa. Este programa mostra todos:
// limites.cs — tamanho e limites de cada tipo numérico
Console.WriteLine($"{"Tipo",-8} {"Bytes",5} {"Mínimo",28} {"Máximo",28}");
Linha("sbyte", sizeof(sbyte), sbyte.MinValue, sbyte.MaxValue);
Linha("byte", sizeof(byte), byte.MinValue, byte.MaxValue);
Linha("short", sizeof(short), short.MinValue, short.MaxValue);
Linha("ushort", sizeof(ushort), ushort.MinValue, ushort.MaxValue);
Linha("int", sizeof(int), int.MinValue, int.MaxValue);
Linha("uint", sizeof(uint), uint.MinValue, uint.MaxValue);
Linha("long", sizeof(long), long.MinValue, long.MaxValue);
Linha("ulong", sizeof(ulong), ulong.MinValue, ulong.MaxValue);
Linha("char", sizeof(char), (int)char.MinValue, (int)char.MaxValue);
Linha("float", sizeof(float), float.MinValue, float.MaxValue);
Linha("double", sizeof(double), double.MinValue, double.MaxValue);
Linha("decimal", sizeof(decimal), decimal.MinValue, decimal.MaxValue);
Linha("bool", sizeof(bool), 0, 1);
static void Linha(string tipo, int bytes, object min, object max) =>
Console.WriteLine($"{tipo,-8} {bytes,5} {min,28} {max,28}");
$ dotnet run limites.cs Tipo Bytes Mínimo Máximo sbyte 1 -128 127 byte 1 0 255 short 2 -32768 32767 ushort 2 0 65535 int 4 -2147483648 2147483647 uint 4 0 4294967295 long 8 -9223372036854775808 9223372036854775807 ulong 8 0 18446744073709551615 char 2 0 65535 float 4 -3,4028235E+38 3,4028235E+38 double 8 -1,7976931348623157E+308 1,7976931348623157E+308 decimal 16 -79228162514264337593543950335 79228162514264337593543950335 bool 1 0 1
O E+38 é a notação científica: 3,4028235E+38 é 3,4 × 1038, um número com 39 algarismos. O editor do guia mostra exatamente a mesma tabela.
| Tipo | Nome no .NET | Bytes | Faixa (em palavras) | Literal | Para quê | Aula |
|---|---|---|---|---|---|---|
sbyte | System.SByte | 1 | −128 a 127 | (sbyte)5 | quase nunca | Inteiros |
byte | System.Byte | 1 | 0 a 255 | (byte)5 | bytes de arquivos e imagens | Inteiros |
short | System.Int16 | 2 | −32 768 a 32 767 | (short)5 | economizar memória em arrays enormes | Inteiros |
ushort | System.UInt16 | 2 | 0 a 65 535 | (ushort)5 | raro | Inteiros |
int | System.Int32 | 4 | cerca de ±2,1 bilhões | 5 | o padrão para contar: quantidades, idades, índices | Inteiros |
uint | System.UInt32 | 4 | 0 a cerca de 4,3 bilhões | 5u | raro (interoperar com C) | Inteiros |
long | System.Int64 | 8 | cerca de ±9,2 quintilhões | 5L | quando o int não basta: populações, milissegundos, bytes de um disco | Inteiros |
ulong | System.UInt64 | 8 | 0 a cerca de 18,4 quintilhões | 5ul | raro | Inteiros |
float | System.Single | 4 | ±3,4 × 1038, uns 7 algarismos certos | 5f | gráficos e jogos | float, double e decimal |
double | System.Double | 8 | ±1,8 × 10308, uns 15 a 17 algarismos | 5.0 | o padrão para medidas: altura, distância, ciência | float, double e decimal |
decimal | System.Decimal | 16 | ±7,9 × 1028, 28 a 29 algarismos, exato na base 10 | 5m | dinheiro | float, double e decimal |
char | System.Char | 2 | códigos 0 a 65 535 (UTF-16) | 'a' | um caractere | char e bool |
bool | System.Boolean | 1 | false ou true | true | sim ou não | char e bool |
string | System.String | (varia) | até cerca de 1 bilhão de caracteres | "a" | texto | Strings |
object | System.Object | (varia) | qualquer valor | — | a base de todos os tipos | A Classe object |
- Os tamanhos são iguais em todo computador: no C#,
inté sempre 32 bits, no Windows, no Linux, no celular e no editor do guia. (Em C, isso não é garantido.) - Os tipos sem sinal (
byte,ushort,uint,ulong) começam no 0 e vão até o dobro. Oué de "unsigned", "sem sinal". O único sem sinal comum é obyte; osbyte("signed byte") é o contrário. - O
sizeoffunciona com estes tipos sem nada especial. Com outros (structs suas,nint), ele pede códigounsafe(erroCS0233).
Precisão: float, double e decimal
Os três guardam números com vírgula, mas com precisão diferente. A conta 1 ÷ 3 mostra até onde cada um vai, e 0,1 + 0,2 mostra o erro de arredondamento do double:
// flutuante.cs — os valores especiais do float e do double
using System.Runtime.CompilerServices;
Console.WriteLine($"double.Epsilon = {double.Epsilon}");
Console.WriteLine($"float.Epsilon = {float.Epsilon}");
Console.WriteLine($"1.0 / 0 = {1.0 / 0}");
Console.WriteLine($"-1.0 / 0 = {-1.0 / 0}");
Console.WriteLine($"0.0 / 0 = {0.0 / 0}");
Console.WriteLine($"0.1 + 0.2 = {0.1 + 0.2}");
Console.WriteLine($"0.1f + 0.2f = {0.1f + 0.2f}");
Console.WriteLine($"0.1m + 0.2m = {0.1m + 0.2m}");
Console.WriteLine($"1.0 / 3 = {1.0 / 3}");
Console.WriteLine($"1.0f / 3 = {1.0f / 3}");
Console.WriteLine($"1.0m / 3 = {1.0m / 3}");
double nan = double.NaN;
Console.WriteLine($"NaN == NaN: {nan == double.NaN}");
Console.WriteLine($"double.IsNaN(0.0 / 0): {double.IsNaN(0.0 / 0)}");
Console.WriteLine($"Half: {Unsafe.SizeOf<Half>()} bytes, máximo {Half.MaxValue}");
Console.WriteLine($"Int128: {Unsafe.SizeOf<Int128>()} bytes");
Console.WriteLine($"Int128.MaxValue = {Int128.MaxValue}");
Console.WriteLine($"nint.Size = {nint.Size}");
$ dotnet run flutuante.cs double.Epsilon = 5E-324 float.Epsilon = 1E-45 1.0 / 0 = ∞ -1.0 / 0 = -∞ 0.0 / 0 = NaN 0.1 + 0.2 = 0,30000000000000004 0.1f + 0.2f = 0,3 0.1m + 0.2m = 0,3 1.0 / 3 = 0,3333333333333333 1.0f / 3 = 0,33333334 1.0m / 3 = 0,3333333333333333333333333333 NaN == NaN: False double.IsNaN(0.0 / 0): True Half: 2 bytes, máximo 65500 Int128: 16 bytes Int128.MaxValue = 170141183460469231731687303715884105727 nint.Size = 8
| Tipo | 1 ÷ 3 | Algarismos certos | 0,1 + 0,2 | Base | Use para |
|---|---|---|---|---|---|
float | 0,33333334 | uns 7 (o último já está errado) | 0,3 (o erro existe, mas some no arredondamento da tela) | 2 | gráficos, jogos, sensores |
double | 0,3333333333333333 | 15 a 17 | 0,30000000000000004 | 2 | medidas e ciência |
decimal | 0,3333333333333333333333333333 | 28 a 29 | 0,3 (exato) | 10 | dinheiro |
Por que o double erra em 0,1 + 0,2? Ele guarda os números em base 2, e 0,1 em base 2 é uma dízima infinita (como 1/3 em base 10). O decimal guarda em base 10, por isso 0,1 é exato. É a razão de o guia usar decimal para dinheiro, sempre. Aula: float, double e decimal.
Valores especiais do float e do double
| Constante | Valor (lab) | O que é |
|---|---|---|
double.Epsilon | 5E-324 | o menor double positivo. Não serve para comparar números com tolerância; para isso, use algo como Math.Abs(a - b) < 1e-9. |
float.Epsilon | 1E-45 | o menor float positivo |
double.PositiveInfinity | ∞ | o resultado de 1.0 / 0. Dividir um double por zero não dá erro. |
double.NegativeInfinity | -∞ | o resultado de -1.0 / 0 |
double.NaN | NaN | "not a number", não é um número: 0.0 / 0, Math.Sqrt(-1). É diferente de tudo, até de si mesmo (NaN == NaN é False): teste com double.IsNaN(x). |
Com inteiros é diferente: 7 / 0 com uma variável lança DivideByZeroException, e com constantes nem compila (veja Exceções e Erros do Compilador).
Outros tipos numéricos
| Tipo | Bytes | Limite (lab) | O que é | Desde |
|---|---|---|---|---|
nint, nuint | 8 no lab, 4 no editor | nint.MaxValue: 9223372036854775807 no lab, 2147483647 no editor | inteiro do tamanho de um endereço de memória: 64 bits num Linux de 64 bits, 32 no WebAssembly do editor. Use nint.Size para saber. | C# 9 |
Half | 2 | 65500 | ponto flutuante de 16 bits (inteligência artificial, placas de vídeo). Não tem palavra-chave nem literal. | .NET 5 |
Int128, UInt128 | 16 | Int128.MaxValue = 170141183460469231731687303715884105727 | inteiros de 128 bits, sem palavra-chave | .NET 7 |
BigInteger | (varia) | sem limite | inteiro de qualquer tamanho (using System.Numerics;), mais lento | .NET 4 |
O tipo de cada literal
Um número escrito no código (um literal) já tem tipo. O GetType().Name mostra qual:
// nomes.cs — o nome .NET de cada apelido, e o tipo de cada literal
Console.WriteLine($"{typeof(int).FullName} {typeof(string).FullName} {typeof(bool).FullName} {typeof(decimal).FullName}");
object[] literais = [5, 5L, 5u, 5ul, 3_000_000_000, 5.0, 5f, 5m, 5e3, 'a', "a", true, 0x1F, 0b1010];
string[] textos = ["5", "5L", "5u", "5ul", "3_000_000_000", "5.0", "5f", "5m", "5e3", "'a'", "\"a\"", "true", "0x1F", "0b1010"];
for (int i = 0; i < literais.Length; i++)
{
Console.WriteLine($"{textos[i],-14} {literais[i].GetType().Name,-8} {literais[i]}");
}
$ dotnet run nomes.cs System.Int32 System.String System.Boolean System.Decimal 5 Int32 5 5L Int64 5 5u UInt32 5 5ul UInt64 5 3_000_000_000 UInt32 3000000000 5.0 Double 5 5f Single 5 5m Decimal 5 5e3 Double 5000 'a' Char a "a" String a true Boolean True 0x1F Int32 31 0b1010 Int32 10
| Literal | Tipo | Regra |
|---|---|---|
5, -40 | int | inteiro sem sufixo: int; se não cabe, uint, depois long, depois ulong. Por isso 3_000_000_000 é uint. |
5L | long | sufixo L (use o maiúsculo: o l minúsculo parece o número 1) |
5u, 5ul | uint, ulong | sufixos u e ul (maiúsculas ou minúsculas) |
5.0, 5e3 | double | com ponto ou expoente, sem sufixo: double. No código, o separador é sempre o ponto, em qualquer país. |
5f | float | sufixo f ou F |
5m | decimal | sufixo m ou M ("money") |
0x1F, 0b1010 | int | hexadecimal (0x) e binário (0b, C# 7) |
1_000_000 | int | o _ separa os milhares só para quem lê; o compilador ignora C# 7 |
'a' | char | aspas simples, um caractere |
"a" | string | aspas duplas (aula Escapes e Strings Literais) |
true, false | bool | — |
Valor padrão (default)
Campos de uma classe, itens de um array novo e default(T) começam com o valor padrão do tipo. (Variáveis locais, não: o compilador exige que você dê um valor antes de usar.)
// padroes.cs — valor padrão (default) de cada tipo
Console.WriteLine($"int: [{default(int)}]");
Console.WriteLine($"double: [{default(double)}]");
Console.WriteLine($"decimal: [{default(decimal)}]");
Console.WriteLine($"bool: [{default(bool)}]");
Console.WriteLine($"char: [{(int)default(char)}] (o caractere de código 0)");
Console.WriteLine($"string: [{default(string) ?? "null"}]");
Console.WriteLine($"int?: [{(default(int?) is null ? "null" : "?")}]");
Console.WriteLine($"DateTime: [{default(DateTime)}]");
$ dotnet run padroes.cs int: [0] double: [0] decimal: [0] bool: [False] char: [0] (o caractere de código 0) string: [null] int?: [null] DateTime: [01/01/0001 00:00:00]
| Tipo | Valor padrão | Aula |
|---|---|---|
| todos os números | 0 | Variáveis |
bool | false | char e bool |
char | '\0' (o caractere de código 0, invisível) | char e bool |
string, classes, arrays | null | null |
int? e outros anuláveis | null | null |
structs (DateTime…) | todos os campos no padrão: DateTime vira 1º de janeiro do ano 1, meia-noite | Structs |
Estouro e conversões
O que acontece quando o valor não cabe no tipo, ou quando um double vira int:
// estouro.cs — o que acontece quando passa do limite
int maior = int.MaxValue;
Console.WriteLine($"int.MaxValue + 1 = {maior + 1}");
byte b = 255;
b++;
Console.WriteLine($"byte 255 + 1 = {b}");
Console.WriteLine($"(int)3.99 = {(int)3.99}");
Console.WriteLine($"(int)-3.99 = {(int)-3.99}");
double grande = 1e20, nada = double.NaN;
Console.WriteLine($"(int)1e20 = {(int)grande}");
Console.WriteLine($"(int)NaN = {(int)nada}");
int trezentos = 300;
Console.WriteLine($"(byte)300 = {(byte)trezentos}");
Console.WriteLine($"Math.Round(2.5) = {Math.Round(2.5)}, Math.Round(3.5) = {Math.Round(3.5)}");
Console.WriteLine($"Convert.ToInt32(2.5) = {Convert.ToInt32(2.5)}, Convert.ToInt32(3.5) = {Convert.ToInt32(3.5)}");
Console.WriteLine($"7 / 2 = {7 / 2}, 7 % 2 = {7 % 2}, -7 / 2 = {-7 / 2}, -7 % 2 = {-7 % 2}");
Console.WriteLine(checked(maior + 1));
$ dotnet run estouro.cs int.MaxValue + 1 = -2147483648 byte 255 + 1 = 0 (int)3.99 = 3 (int)-3.99 = -3 (int)1e20 = 2147483647 (int)NaN = 0 (byte)300 = 44 Math.Round(2.5) = 2, Math.Round(3.5) = 4 Convert.ToInt32(2.5) = 2, Convert.ToInt32(3.5) = 4 7 / 2 = 3, 7 % 2 = 1, -7 / 2 = -3, -7 % 2 = -1 Unhandled exception. System.OverflowException: Arithmetic operation resulted in an overflow. at Program.<Main>$(String[] args) in /home/ana/cs/ref/estouro.cs:line 17 $ echo $? 134
| Situação | Resultado (lab e editor) | Por quê | Aula |
|---|---|---|---|
int.MaxValue + 1 | -2147483648 | o inteiro "dá a volta", sem aviso (o padrão é unchecked) | Inteiros |
checked(int.MaxValue + 1) | OverflowException | com checked, o estouro vira exceção | Inteiros |
byte b = 255; b++; | 0 | também dá a volta | ++ e -- |
(int)3.99, (int)-3.99 | 3, -3 | o cast corta a parte decimal, na direção do zero; não arredonda | Conversão de Tipos |
(int) de um double grande demais | 2147483647 | desde o .NET 9, a conversão "satura": para no limite do tipo (int.MaxValue, ou int.MinValue para os negativos). O editor do guia faz igual. Versões antigas do .NET davam outro valor: confira o limite antes | Conversão de Tipos |
(int) de NaN | 0 | também desde o .NET 9 (igual no editor): teste com double.IsNaN antes | Conversão de Tipos |
(byte)300 (de uma variável) | 44 | fica só o byte de baixo: 300 − 256 = 44 | Conversão de Tipos |
Math.Round(2.5), Math.Round(3.5) | 2, 4 | arredonda o ",5" para o par mais próximo ("arredondamento bancário"). Para o arredondamento da escola, Math.Round(x, MidpointRounding.AwayFromZero) | Math |
Convert.ToInt32(2.5) | 2 | arredonda como o Math.Round (ao contrário do cast, que corta) | Conversão de Tipos |
7 / 2, -7 / 2 | 3, -3 | divisão de inteiros corta a parte decimal | Operadores Aritméticos |
-7 % 2 | -1 | o resto tem o sinal de quem é dividido | Operadores Aritméticos |
Conversões que acontecem sozinhas (implícitas)
O C# converte sozinho quando não há risco de perder informação. Quando há, ele exige o cast (tipo):
| De | Vira sozinho em | Precisa de cast para |
|---|---|---|
byte | short, ushort, int, uint, long, ulong, float, double, decimal | sbyte, char |
int | long, float, double, decimal | byte, short, uint, char… |
long | float, double, decimal | int e menores |
char | ushort, int, long, double… | byte, short |
float | double | todos os inteiros, decimal |
double | (nenhum) | todos os inteiros, float, decimal |
decimal | (nenhum) | todos os inteiros, float, double |
Repare: int → float é implícito mas pode perder algarismos (o float só tem uns 7). E double e decimal nunca viram um no outro sozinhos. Aula: Conversão de Tipos.
Quando dá errado
| Código | Mensagem real (lab) | O que significa e como consertar | Aula |
|---|---|---|---|
decimal preco = 15.90; | error CS0664: Literal do tipo double não pode ser convertido implicitamente no tipo "decimal"; use um sufixo "M" para criar um literal desse tipo | 15.90 é double. Escreva 15.90m. | float, double e decimal |
float altura = 1.65; | error CS0664: … no tipo "float"; use um sufixo "F" … | Escreva 1.65f (ou use double). | float, double e decimal |
int populacao = 3_000_000_000; | error CS0266: Não é possível converter implicitamente tipo "uint" em "int". Existe uma conversão explícita (há uma conversão ausente?) | O número não cabe no int (o literal virou uint). Use long. | Inteiros |
byte b = (byte)300; | error CS0221: O valor de constante "300" não pode ser convertido em "byte" (use a sintaxe "unchecked" para substituir) | Com uma constante, o compilador vê o estouro e recusa. Com uma variável, o cast passa e dá 44. | Conversão de Tipos |
(int)1e20 | error CS0221: O valor de constante "1E+20" não pode ser convertido em "int" … | O mesmo, com um double constante. | Conversão de Tipos |
sizeof(nint) | error CS0233: 'nint' não tem um tamanho predefinido, portanto, sizeof só pode ser usado em um contexto desprotegido | Use nint.Size. | Memória |
checked(maior + 1) | Unhandled exception. System.OverflowException: Arithmetic operation resulted in an overflow. | A conta passou do limite dentro de um checked. Use um tipo maior (long), ou trate a exceção. | try e catch |
A Ana somou a população de três países num int:
// A população de três países, somada
int brasil = 213_000_000;
int india = 1_450_000_000;
int china = 1_410_000_000;
int soma = brasil + india + china;
Console.WriteLine($"Soma: {soma}");
$ dotnet run populacao.cs Soma: -1221967296
O que aconteceu: cada população cabe no int (o máximo é 2 147 483 647), mas a soma, 3 073 000 000, não. A conta deu a volta e virou um número negativo, sem erro nem aviso. É o defeito mais traiçoeiro dos inteiros.
Como consertar: use long quando a conta pode passar de 2 bilhões. Para que um estouro vire erro em vez de número errado, use checked na conta, ou ligue <CheckForOverflowUnderflow>true</CheckForOverflowUnderflow> no .csproj (aula O Arquivo .csproj).
Experimente
Missão: conserte o programa da população para mostrar a soma certa, Soma: 3073000000. Dica: o tipo que cabe tudo isso tem 8 bytes.
// A população de três países, somada
int brasil = 213_000_000;
int india = 1_450_000_000;
int china = 1_410_000_000;
int soma = brasil + india + china;
Console.WriteLine($"Soma: {soma}");
// A população de três países, somada
long brasil = 213_000_000;
long india = 1_450_000_000;
long china = 1_410_000_000;
long soma = brasil + india + china;
Console.WriteLine($"Soma: {soma}");
Experimente: troque os tipos e os valores e veja os limites. O que acontece com short.MaxValue + 1? E com (short)(short.MaxValue + 1)? E com 0.1f + 0.7f?
Console.WriteLine($"short vai de {short.MinValue} a {short.MaxValue}");
Console.WriteLine(short.MaxValue + 1);
Console.WriteLine(1.0 / 3);
Console.WriteLine(1.0m / 3);
Console.WriteLine(0.1 + 0.2 == 0.3);
Console.WriteLine(0.1m + 0.2m == 0.3m);
Resumo
- Os tamanhos são fixos:
byte1,short2,int4,long8,float4,double8,decimal16,char2 bytes. Só onintmuda (8 no lab, 4 no editor). intvai até 2 147 483 647; para mais,long. Para dinheiro,decimal; para medidas,double.- Sufixos:
L(long),u,ul,f(float),m(decimal). Sem sufixo, inteiro éinte número com ponto édouble. - Inteiro que passa do limite dá a volta sem aviso; com
checked, lançaOverflowException. - O cast
(int)corta a parte decimal;Math.RoundeConvert.ToInt32arredondam para o par. doubledividido por zero dá∞ouNaN;NaNse testa comdouble.IsNaN.