C# Inteiros

Números inteiros estão em todo lugar: a quantidade de cadernos da lojinha, a idade de um cliente, os pontos de um jogo, o número de habitantes de um país. O C# tem uma família de tipos inteiros, de tamanhos diferentes, com e sem sinal. Nesta aula você vai conhecer cada um, ver até onde cada caixa vai e descobrir o que acontece quando uma conta passa do limite, e como fazer o C# avisar quando isso acontece.

Você vai aprender
  • A família: byte, short, int, long e as versões sem sinal (sbyte, ushort, uint, ulong).
  • Os limites de cada tipo, com MinValue e MaxValue.
  • Como escrever inteiros: 1_000_000, 0xFF, 0b1010 e os sufixos L, U e UL.
  • Os erros de valor que não cabe: CS0031, CS0266 e CS1021.
  • O estouro (overflow): por que int.MaxValue + 1 dá um número negativo.
  • checked e unchecked, e a exceção OverflowException.

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 hodômetro do carro

O hodômetro de um carro antigo tem 6 rodinhas: mostra de 000000 a 999999 quilômetros. Ele não consegue mostrar um milhão. Se o carro rodar mais 1 km depois de 999999, as rodinhas voltam para 000000.

Os inteiros do computador são iguais: cada tipo tem um número fixo de "rodinhas" (bits) e, por isso, um maior valor e um menor valor. Para números maiores, você precisa de uma caixa com mais rodinhas. E quando uma conta passa do limite, o C# deixa você escolher: dar a volta, como o hodômetro, ou parar o programa com um erro (seção 5).


1. A família dos inteiros

byte idade = 29;
short ano = 2026;
int habitantes = 213_000_000;
long populacaoMundial = 8_100_000_000;

Console.WriteLine(idade);
Console.WriteLine(ano);
Console.WriteLine(habitantes);
Console.WriteLine(populacaoMundial);
$ dotnet run familia.cs
29
2026
213000000
8100000000
byte idade = 29;
byte: 1 byte, de 0 a 255. Pequeno e sem negativos. Aparece muito ao ler arquivos e imagens, byte a byte.
short ano = 2026;
short ("curto"): 2 bytes, de -32 768 a 32 767. Quase não se usa no dia a dia.
int habitantes = 213_000_000;
int: 4 bytes, até uns 2,1 bilhões. É o padrão: na dúvida, use int. Os _ só ajudam a ler o número (seção 3).
long populacaoMundial = 8_100_000_000;
long ("longo"): 8 bytes, até uns 9,2 quintilhões. Para números que passam de 2 bilhões: população, bytes de um disco, milissegundos.

Na prática, você vai usar int quase sempre e long quando o número pode passar de 2 bilhões. Os outros existem para economizar memória ou para conversar com arquivos e outros sistemas.

Com sinal e sem sinal

Cada tamanho tem duas versões: uma com sinal (guarda negativos) e uma sem sinal (só zero e positivos, mas vai até o dobro). A letra u na frente quer dizer unsigned, "sem sinal"; o s do sbyte quer dizer signed, "com sinal":

BytesCom sinalSem sinal
1sbytebyte
2shortushort
4intuint
8longulong

Repare que o byte é a exceção: o "normal" dele é sem sinal, e o com sinal ganhou o nome sbyte.


2. Os limites: MinValue e MaxValue

Todo tipo inteiro sabe o seu menor valor (MinValue, "valor mínimo") e o seu maior valor (MaxValue, "valor máximo"):

Console.WriteLine($"sbyte:  {sbyte.MinValue} a {sbyte.MaxValue}");
Console.WriteLine($"byte:   {byte.MinValue} a {byte.MaxValue}");
Console.WriteLine($"short:  {short.MinValue} a {short.MaxValue}");
Console.WriteLine($"ushort: {ushort.MinValue} a {ushort.MaxValue}");
Console.WriteLine($"int:    {int.MinValue} a {int.MaxValue}");
Console.WriteLine($"uint:   {uint.MinValue} a {uint.MaxValue}");
Console.WriteLine($"long:   {long.MinValue} a {long.MaxValue}");
Console.WriteLine($"ulong:  {ulong.MinValue} a {ulong.MaxValue}");
$ dotnet run limites.cs
sbyte:  -128 a 127
byte:   0 a 255
short:  -32768 a 32767
ushort: 0 a 65535
int:    -2147483648 a 2147483647
uint:   0 a 4294967295
long:   -9223372036854775808 a 9223372036854775807
ulong:  0 a 18446744073709551615
TipoBytesFaixaEm palavras
sbyte1-128 a 127pouco mais de cem
byte10 a 255duzentos e poucos
short2-32 768 a 32 767uns 32 mil
ushort20 a 65 535uns 65 mil
int4-2 147 483 648 a 2 147 483 647uns 2,1 bilhões
uint40 a 4 294 967 295uns 4,2 bilhões
long8-9 223 372 036 854 775 808 a 9 223 372 036 854 775 807uns 9,2 quintilhões
ulong80 a 18 446 744 073 709 551 615uns 18 quintilhões

O lado negativo sempre tem um a mais (-128 e 127): o zero ocupa uma vaga do lado positivo. E o tipo sem sinal vai até o dobro, porque usa para os positivos as vagas que seriam dos negativos. Esses tamanhos são fixos no C#: um long tem 8 bytes em qualquer computador.

Ver o número com separador de milhar

Console.WriteLine(1234567.ToString("N0")); mostra 1.234.567 no Ubuntu em português (o N0 quer dizer "número com 0 casas decimais"). Os formatos estão em Formatar Números.

Quando o valor não cabe na caixa

Se você escreve um número que não cabe no tipo, o C# nem compila. Três tentativas e três erros:

byte nota = 300;
Console.WriteLine(nota);
$ dotnet run naocabe.cs
/home/ana/cs/naocabe.cs(1,13): error CS0031: O valor de constante "300" não pode ser convertido em "byte"

Ocorreu uma falha no build. Corrija os erros de build e execute novamente.

O byte vai até 255; 300 não cabe. Em inglês: Constant value '300' cannot be converted to a 'byte'. O mesmo erro aparece com uint estoque = -1;: um tipo sem sinal não aceita negativos.

int populacao = 8100000000;
Console.WriteLine(populacao);
$ dotnet run naocabe2.cs
/home/ana/cs/naocabe2.cs(1,17): 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.

Aqui a mensagem fala em long, e você nem escreveu long. É que o C# dá a todo número escrito no código (um literal) o primeiro tipo em que ele cabe, nesta ordem: int, uint, long, ulong. O 8100000000 não cabe em int nem em uint, então ele é um long, e um long não entra numa caixa int.

ulong enorme = 99999999999999999999;
Console.WriteLine(enorme);
$ dotnet run naocabe3.cs
/home/ana/cs/naocabe3.cs(1,16): error CS1021: Constante de integral muito grande

Ocorreu uma falha no build. Corrija os erros de build e execute novamente.

Esse número (20 dígitos) passa até do ulong, o maior inteiro comum. Em inglês: Integral constant is too large. Para números assim, veja "Inteiros ainda maiores" no fim da seção 5.

Missão: a população do mundo passa de 8 bilhões e não cabe num int. Rode para ver o erro CS0266 e troque o tipo da variável para o inteiro de 8 bytes, para mostrar 8100000000.

int populacao = 8_100_000_000;
Console.WriteLine(populacao);
long populacao = 8_100_000_000;
Console.WriteLine(populacao);

3. Como escrever inteiros (literais)

int milhao = 1_000_000;
int hexa = 0xFF;
int binario = 0b1010_1010;
int dez = 010;
long grande = 5_000_000_000L;
uint semSinal = 4_000_000_000U;
ulong maior = 18_000_000_000_000_000_000UL;
Console.WriteLine(milhao);
Console.WriteLine(hexa);
Console.WriteLine(binario);
Console.WriteLine(dez);
Console.WriteLine(grande);
Console.WriteLine(semSinal);
Console.WriteLine(maior);
Console.WriteLine(255.ToString("X"));
Console.WriteLine(10.ToString("B"));
$ dotnet run literais.cs
1000000
255
170
10
5000000000
4000000000
18000000000000000000
FF
1010
1_000_000
O separador de dígitos C# 7: o _ só ajuda os seus olhos e é ignorado. Pode ficar entre quaisquer dígitos: 1_000_000, 12_34. Não aparece na saída.
0xFF
Hexadecimal, base 16: começa com 0x e usa os dígitos 0 a 9 e A a F (A = 10 … F = 15). FF = 15 × 16 + 15 = 255. Muito usado para cores (0xFF8000 é laranja).
0b1010_1010
Binário, base 2 C# 7: começa com 0b e só usa 0 e 1. 10101010 em binário é 128 + 32 + 8 + 2 = 170.
010
Dez mesmo! No C#, ao contrário do C e do Java, um zero à esquerda não muda a base: não existe literal octal. O zero é só ignorado.
5_000_000_000L
O sufixo L diz "este número é long". Aqui ele nem era preciso (o número já seria long, porque não cabe em uint), mas ele é útil para forçar uma conta a ser feita em long (seção 4). Use L maiúsculo: o l minúsculo parece o dígito 1, e o compilador até avisa (CS0078).
4_000_000_000U … UL
U = uint (ou ulong, se não couber); UL = ulong. As letras podem ser maiúsculas ou minúsculas.
255.ToString("X") · 10.ToString("B")
O caminho inverso: mostrar um número em hexadecimal ("X") ou em binário ("B", novo no .NET 8).

4. Estouro: quando a conta passa do limite

Escrever um número que não cabe é fácil de pegar: o compilador vê o número. Mas e quando o número só aparece durante o programa, como resultado de uma conta? Veja:

int pontos = int.MaxValue;
Console.WriteLine(pontos);
pontos = pontos + 1;
Console.WriteLine(pontos);
byte b = 255;
b++;
Console.WriteLine(b);
$ dotnet run estouro.cs
2147483647
-2147483648      « o maior int + 1 virou o MENOR int
0                « e o byte 255 + 1 virou 0
int pontos = int.MaxValue;
O jogador começa com o maior int possível, 2 147 483 647.
pontos = pontos + 1;
Mais um ponto. O resultado certo (2 147 483 648) não cabe num int.
-2147483648
O número deu a volta, como o hodômetro: do maior positivo para o menor negativo. Nenhum erro, nenhum aviso: o programa simplesmente seguiu com o valor errado.
b++;
Soma 1 ao byte (o ++ tem aula própria, ++ e --). 255 + 1 não cabe em 1 byte e volta para 0.

Isso é o estouro (overflow, "transbordar"). Diferente do C, no C# o resultado é sempre esse, em qualquer computador: a conta guarda só os bits que cabem. Mas "sempre igual" não quer dizer "certo". Um saldo que vira negativo de repente é um defeito grave.

Um tipo sem sinal dá a volta para o outro lado. A Ana tinha 3 cadernos e vendeu 5:

uint estoque = 3;
uint vendidos = 5;
uint resta = estoque - vendidos;
Console.WriteLine(resta);
$ dotnet run subtrai.cs
4294967294       « 3 - 5 deu quase 4,3 bilhões

O uint não fica negativo: 3 - 5 = -2 vira 4 294 967 296 - 2. Por isso a recomendação é usar int para contas comuns, mesmo de coisas que "nunca são negativas". Com int, o resultado seria -2, que pelo menos mostra que faltam 2 cadernos.

O estouro escondido na conta

Guardar o resultado num tipo grande não basta, se a conta foi feita num tipo pequeno:

int anos = 100;
long segundos = anos * 365 * 24 * 60 * 60;
Console.WriteLine(segundos);
long certo = anos * 365L * 24 * 60 * 60;
Console.WriteLine(certo);
$ dotnet run escondido.cs
-1141367296      « errado!
3153600000       « certo: uns 3,15 bilhões de segundos

A caixa segundos é long, mas o C# faz a conta primeiro e só depois guarda. Na conta só há int (anos, 365, 24, 60), então ela é feita em int, estoura no caminho, e o valor já errado é guardado. Na segunda linha, o 365L é long, e uma conta entre int e long é feita em long. A regra completa está em Conversão de Tipos.


5. checked: estouro vira erro

O estouro do C# é silencioso por padrão porque conferir cada conta custa um pouquinho de tempo. Quando um valor errado seria grave (dinheiro, estoque, contagens importantes), você pode pedir para o C# conferir, com a palavra checked ("conferido"):

int pontos = int.MaxValue;
Console.WriteLine("Antes da conta");
int novo = checked(pontos + 1);
Console.WriteLine(novo);
$ dotnet run checked1.cs
Antes da conta
Unhandled exception. System.OverflowException: Arithmetic operation resulted in an overflow.
   at Program.<Main>$(String[] args) in /home/ana/cs/checked1.cs:line 3
$ echo $?
134
checked(pontos + 1)
Faz a conta conferindo: se o resultado não couber no tipo, em vez de dar a volta, o programa para com um erro.
Unhandled exception.
"Exceção não tratada": aconteceu um erro durante a execução e ninguém cuidou dele. As exceções saem em inglês, mesmo no Ubuntu em português.
System.OverflowException
O tipo do erro: "exceção de estouro".
Arithmetic operation resulted in an overflow.
"A operação aritmética resultou em estouro."
at Program.<Main>$(String[] args) in /home/ana/cs/checked1.cs:line 3
Onde foi: no programa principal (o <Main>$ é o nome que o compilador dá às instruções soltas do arquivo, aula Program.cs e o Main), no arquivo checked1.cs, linha 3.
134
O código de saída de um programa que morreu com exceção no Linux. O Console.WriteLine(novo) nunca rodou.

Parar com erro parece pior do que seguir, mas é muito melhor do que guardar um saldo errado sem ninguém perceber. Mais tarde você vai aprender a tratar o erro, com try e catch.

Um bloco inteiro conferido

checked também pode envolver várias linhas, entre chaves. Toda conta de inteiros dentro do bloco é conferida:

int saldo = 2_000_000_000;
int deposito = 500_000_000;
checked
{
    int total = saldo + deposito;
    Console.WriteLine(total);
}
$ dotnet run checked2.cs
Unhandled exception. System.OverflowException: Arithmetic operation resulted in an overflow.
   at Program.<Main>$(String[] args) in /home/ana/cs/checked2.cs:line 5

Sem o checked, o programa mostraria -1794967296: um depósito que deixou o saldo negativo.

Missão: o estoque da Ana tem 50 000 caixas com 50 000 parafusos cada. A conta estoura e o programa mostra um número sem sentido. Envolva a multiplicação com checked( … ) para o programa parar com OverflowException em vez de mostrar o valor errado.

int caixas = 50_000;
int porCaixa = 50_000;
int parafusos = caixas * porCaixa;
Console.WriteLine("Parafusos: " + parafusos);
int caixas = 50_000;
int porCaixa = 50_000;
int parafusos = checked(caixas * porCaixa);
Console.WriteLine("Parafusos: " + parafusos);

Depois de cumprir a missão, troque os dois int por long (inclusive o de parafusos): agora o resultado, 2 500 000 000, cabe, e o checked deixa a conta passar.

Ligar o checked no programa todo

Em vez de escrever checked em cada conta, você pode ligar a conferência para o programa inteiro com a opção CheckForOverflowUnderflow ("conferir estouro para cima e para baixo"). Num arquivo solto, ela vai numa linha #:property no topo .NET 10; num projeto, no .csproj (aula O Arquivo .csproj). Com ela ligada, unchecked ("não conferido") abre uma exceção, para as contas em que dar a volta é o que você quer:

#:property CheckForOverflowUnderflow=true
int pontos = int.MaxValue;
int a = unchecked(pontos + 1);
Console.WriteLine(a);
int b = pontos + 1;
Console.WriteLine(b);
$ dotnet run checkedprop.cs
-2147483648
Unhandled exception. System.OverflowException: Arithmetic operation resulted in an overflow.
   at Program.<Main>$(String[] args) in /home/ana/cs/checkedprop.cs:line 5

A linha 3, marcada com unchecked, deu a volta; a linha 5, uma conta comum, agora é conferida e parou o programa.

No editor do guia

O editor do guia também lê a linha #:property CheckForOverflowUnderflow=true: cole este programa numa caixa e rode, e a saída é a mesma do terminal, com o -2147483648 e a OverflowException da linha 5. Num projeto, a mesma opção vai no .csproj.

Contas só com constantes: o compilador confere sozinho

Se a conta só tem valores fixos, o compilador faz a conta na hora de compilar, e ali ele sempre confere:

int pontos = int.MaxValue + 1;
Console.WriteLine(pontos);
$ dotnet run estouroconst.cs
/home/ana/cs/estouroconst.cs(1,14): error CS0220: A operação estoura o tempo de compilação no modo de ativação

Ocorreu uma falha no build. Corrija os erros de build e execute novamente.

A tradução para o português ficou estranha. O original em inglês é mais claro: The operation overflows at compile time in checked mode, "a operação estoura durante a compilação, no modo conferido". Se você quer mesmo esse resultado, escreva unchecked(int.MaxValue + 1): compila e vale -2147483648.

Inteiros ainda maiores

Quando nem o ulong basta, o .NET tem Int128 .NET 7 (16 bytes) e System.Numerics.BigInteger, que cresce o quanto precisar:

Console.WriteLine(Int128.MaxValue);
System.Numerics.BigInteger fat = 1;
for (int i = 1; i <= 30; i++) fat *= i;
Console.WriteLine(fat);
$ dotnet run grandes.cs
170141183460469231731687303715884105727
265252859812191058636308480000000

A segunda linha é o fatorial de 30 (1 × 2 × 3 × … × 30), que passa de qualquer long. O for só repete a multiplicação 30 vezes (aula for). Esses tipos são mais lentos e raros no dia a dia; você vai saber quando precisar deles.


Quando dá errado

error CS0031: O valor de constante "300" não pode ser convertido em "byte"

O que significa: o número escrito não cabe no tipo (um byte vai de 0 a 255; um uint não aceita negativos).

Como consertar: use um tipo maior ou com sinal. Na dúvida, int.

error CS0266: Não é possível converter implicitamente tipo "long" em "int"…

O que significa: um número (ou uma conta) do tipo long foi guardado numa caixa int. Pode ser um literal grande, como 8100000000, ou uma variável long.

Como consertar: troque a caixa para long. Se você tem certeza de que o valor cabe, peça a conversão com (int) (aula Conversão de Tipos).

error CS0266: Não é possível converter implicitamente tipo "int" em "byte"… numa soma de dois byte
byte a = 10;
byte b = 20;
byte c = a + b;
Console.WriteLine(c);
$ dotnet run bytesoma.cs
/home/ana/cs/bytesoma.cs(3,10): error CS0266: Não é possível converter implicitamente tipo "int" em "byte". Existe uma conversão explícita (há uma conversão ausente?)

Ocorreu uma falha no build. Corrija os erros de build e execute novamente.

O que significa: uma surpresa do C#: contas com byte, sbyte, short e ushort são sempre feitas em int. a + b é um int, que não entra sozinho num byte.

Como consertar: use int para a conta (int c = a + b;) ou converta o resultado: byte c = (byte)(a + b);.

error CS1021: Constante de integral muito grande

O que significa: o número escrito não cabe nem no ulong.

Como consertar: use Int128 ou BigInteger, ou, se não for um número de verdade (código de barras, número de cartão), guarde como string.

error CS0220: a operação estoura durante a compilação

O que significa: uma conta só com valores fixos, como int.MaxValue + 1, não cabe no tipo.

Como consertar: faça a conta num tipo maior (int.MaxValue + 1L, guardado num long) ou, se a volta é o que você quer, unchecked( … ).

System.OverflowException: Arithmetic operation resulted in an overflow.

O que significa: uma conta dentro de checked (ou com CheckForOverflowUnderflow ligado) passou do limite do tipo. A linha está no fim da mensagem: line 3.

Como consertar: use um tipo maior (long) ou confira os valores antes da conta. Não tire o checked só para o erro sumir: o valor estaria errado do mesmo jeito.

O resultado de uma conta grande saiu negativo (ou enorme), sem erro nenhum

O que significa: estouro silencioso: a conta deu a volta. Muitas vezes a conta foi feita em int e só o resultado ia para um long.

Como consertar: faça pelo menos um número da conta ser long desde o começo (365L, ou uma variável long). Use checked para descobrir onde estoura.


Referência rápida

EscritaO que é
int, longOs inteiros do dia a dia (4 e 8 bytes). Também byte, sbyte, short, ushort, uint, ulong.
int.MaxValue, int.MinValueMaior e menor valor (vale para todos: long.MaxValue, byte.MaxValue…)
1_000_000Separador de dígitos C# 7
0xFF, 0b1010Hexadecimal (255) e binário (10). Não existe octal: 010 é 10.
5L, 5U, 5ULLiteral long, uint, ulong
sem sufixoO primeiro que couber: int, uint, long, ulong
checked(conta), checked { … }Estouro lança OverflowException
unchecked(conta)Estouro dá a volta (o padrão)
#:property CheckForOverflowUnderflow=trueLiga o checked no programa todo
Int128, BigIntegerInteiros maiores que o ulong
n.ToString("N0"), "X", "B"Mostrar com separador de milhar, em hexadecimal, em binário

Exercícios

1. O maior int

Complete para guardar o maior valor que um int aceita:

int limite = int.;

2. Um milhão legível

Escreva um milhão usando o separador de dígitos entre os milhares:

int premio = ;

3. Conferir a conta

Complete para o programa parar com OverflowException se a soma estourar:

int total = (saldo + deposito);

4. O que int x = int.MaxValue; x = x + 1; Console.WriteLine(x); mostra?

5. Quanto vale int x = 010; em C#?

6. byte a = 10, b = 20; byte c = a + b; O que acontece?

7. Por que long s = anos * 365 * 24 * 60 * 60; (com int anos = 100) guarda um valor errado?

Missão final: quantos segundos tem um século?

Missão: o programa calcula quantos segundos existem em 100 anos (de 365 dias), mas mostra um número negativo: a conta estoura o int antes de chegar na caixa long. Conserte para mostrar Segundos em 100 anos: 3153600000. Dica: basta mudar o tipo de anos, ou pôr um L num dos números da conta.

int anos = 100;
long segundos = anos * 365 * 24 * 60 * 60;
Console.WriteLine($"Segundos em {anos} anos: {segundos}");
long anos = 100;
long segundos = anos * 365 * 24 * 60 * 60;
Console.WriteLine($"Segundos em {anos} anos: {segundos}");

Dica extra: envolva a conta errada com checked( … ) antes de consertar. O programa para com OverflowException e mostra a linha exata do estouro.


Resumo

  • Família: byte/sbyte (1 byte), short/ushort (2), int/uint (4), long/ulong (8). Na dúvida, int; acima de 2 bilhões, long.
  • int.MaxValue = 2147483647, long.MaxValue ≈ 9,2 quintilhões. Os tamanhos são iguais em qualquer computador.
  • Literais: 1_000_000, 0xFF, 0b1010, sufixos L, U, UL. Não há octal: 010 é 10.
  • Número escrito que não cabe: erro de compilação (CS0031, CS0266, CS1021, CS0220).
  • Conta que não cabe: por padrão, dá a volta em silêncio (int.MaxValue + 1 = -2147483648; uint 3 - 5 = 4294967294).
  • checked transforma o estouro em System.OverflowException (código de saída 134); unchecked deixa dar a volta.
  • Uma conta só com int é feita em int, mesmo que o resultado vá para um long. Contas com byte e short são feitas em int.