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.

TipoNome no .NETBytesFaixa (em palavras)LiteralPara quêAula
sbyteSystem.SByte1−128 a 127(sbyte)5quase nuncaInteiros
byteSystem.Byte10 a 255(byte)5bytes de arquivos e imagensInteiros
shortSystem.Int162−32 768 a 32 767(short)5economizar memória em arrays enormesInteiros
ushortSystem.UInt1620 a 65 535(ushort)5raroInteiros
intSystem.Int324cerca de ±2,1 bilhões5o padrão para contar: quantidades, idades, índicesInteiros
uintSystem.UInt3240 a cerca de 4,3 bilhões5uraro (interoperar com C)Inteiros
longSystem.Int648cerca de ±9,2 quintilhões5Lquando o int não basta: populações, milissegundos, bytes de um discoInteiros
ulongSystem.UInt6480 a cerca de 18,4 quintilhões5ulraroInteiros
floatSystem.Single4±3,4 × 1038, uns 7 algarismos certos5fgráficos e jogosfloat, double e decimal
doubleSystem.Double8±1,8 × 10308, uns 15 a 17 algarismos5.0o padrão para medidas: altura, distância, ciênciafloat, double e decimal
decimalSystem.Decimal16±7,9 × 1028, 28 a 29 algarismos, exato na base 105mdinheirofloat, double e decimal
charSystem.Char2códigos 0 a 65 535 (UTF-16)'a'um caracterechar e bool
boolSystem.Boolean1false ou truetruesim ou nãochar e bool
stringSystem.String(varia)até cerca de 1 bilhão de caracteres"a"textoStrings
objectSystem.Object(varia)qualquer valor—a base de todos os tiposA 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. O u é de "unsigned", "sem sinal". O único sem sinal comum é o byte; o sbyte ("signed byte") é o contrário.
  • O sizeof funciona com estes tipos sem nada especial. Com outros (structs suas, nint), ele pede código unsafe (erro CS0233).

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
Tipo1 ÷ 3Algarismos certos0,1 + 0,2BaseUse para
float0,33333334uns 7 (o último já está errado)0,3 (o erro existe, mas some no arredondamento da tela)2gráficos, jogos, sensores
double0,333333333333333315 a 170,300000000000000042medidas e ciência
decimal0,333333333333333333333333333328 a 290,3 (exato)10dinheiro

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

ConstanteValor (lab)O que é
double.Epsilon5E-324o 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.Epsilon1E-45o 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.NaNNaN"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

TipoBytesLimite (lab)O que éDesde
nint, nuint8 no lab, 4 no editornint.MaxValue: 9223372036854775807 no lab, 2147483647 no editorinteiro 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
Half265500ponto flutuante de 16 bits (inteligência artificial, placas de vídeo). Não tem palavra-chave nem literal..NET 5
Int128, UInt12816Int128.MaxValue = 170141183460469231731687303715884105727inteiros de 128 bits, sem palavra-chave.NET 7
BigInteger(varia)sem limiteinteiro 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
LiteralTipoRegra
5, -40intinteiro sem sufixo: int; se não cabe, uint, depois long, depois ulong. Por isso 3_000_000_000 é uint.
5Llongsufixo L (use o maiúsculo: o l minúsculo parece o número 1)
5u, 5uluint, ulongsufixos u e ul (maiúsculas ou minúsculas)
5.0, 5e3doublecom ponto ou expoente, sem sufixo: double. No código, o separador é sempre o ponto, em qualquer país.
5ffloatsufixo f ou F
5mdecimalsufixo m ou M ("money")
0x1F, 0b1010inthexadecimal (0x) e binário (0b, C# 7)
1_000_000into _ separa os milhares só para quem lê; o compilador ignora C# 7
'a'charaspas simples, um caractere
"a"stringaspas duplas (aula Escapes e Strings Literais)
true, falsebool—

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]
TipoValor padrãoAula
todos os números0Variáveis
boolfalsechar e bool
char'\0' (o caractere de código 0, invisível)char e bool
string, classes, arraysnullnull
int? e outros anuláveisnullnull
structs (DateTime…)todos os campos no padrão: DateTime vira 1º de janeiro do ano 1, meia-noiteStructs

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çãoResultado (lab e editor)Por quêAula
int.MaxValue + 1-2147483648o inteiro "dá a volta", sem aviso (o padrão é unchecked)Inteiros
checked(int.MaxValue + 1)OverflowExceptioncom checked, o estouro vira exceçãoInteiros
byte b = 255; b++;0também dá a volta++ e --
(int)3.99, (int)-3.993, -3o cast corta a parte decimal, na direção do zero; não arredondaConversão de Tipos
(int) de um double grande demais2147483647desde 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 antesConversão de Tipos
(int) de NaN0também desde o .NET 9 (igual no editor): teste com double.IsNaN antesConversão de Tipos
(byte)300 (de uma variável)44fica só o byte de baixo: 300 − 256 = 44Conversão de Tipos
Math.Round(2.5), Math.Round(3.5)2, 4arredonda 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)2arredonda como o Math.Round (ao contrário do cast, que corta)Conversão de Tipos
7 / 2, -7 / 23, -3divisão de inteiros corta a parte decimalOperadores Aritméticos
-7 % 2-1o resto tem o sinal de quem é divididoOperadores 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):

DeVira sozinho emPrecisa de cast para
byteshort, ushort, int, uint, long, ulong, float, double, decimalsbyte, char
intlong, float, double, decimalbyte, short, uint, char…
longfloat, double, decimalint e menores
charushort, int, long, double…byte, short
floatdoubletodos 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ódigoMensagem real (lab)O que significa e como consertarAula
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 tipo15.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)1e20error 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 desprotegidoUse 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
O estouro silencioso

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: byte 1, short 2, int 4, long 8, float 4, double 8, decimal 16, char 2 bytes. Só o nint muda (8 no lab, 4 no editor).
  • int vai 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 é int e número com ponto é double.
  • Inteiro que passa do limite dá a volta sem aviso; com checked, lança OverflowException.
  • O cast (int) corta a parte decimal; Math.Round e Convert.ToInt32 arredondam para o par.
  • double dividido por zero dá ∞ ou NaN; NaN se testa com double.IsNaN.