Números no C# e .NET

Trabalhar com números no C# é uma das primeiras tarefas de quem começa no .NET, porque quase todo programa calcula, compara ou converte algum valor. Neste guia, você vai conhecer os tipos inteiros e reais, entender as conversões implícitas e explícitas e praticar os operadores aritméticos, de atribuição e de comparação.

O objetivo é direto: fixar os conceitos com exemplos curtos que você pode executar em qualquer projeto de console.

Como os números no C# estão organizados

A linguagem divide os tipos numéricos em dois grandes grupos. De um lado ficam os tipos inteiros, que guardam valores sem casas decimais, e do outro ficam os tipos reais, também chamados de ponto flutuante.

Todos esses tipos são tipos de valor, ou seja, a variável guarda o próprio número, e não uma referência para ele. Além disso, cada palavra-chave do C# é um apelido para uma estrutura do .NET, como int para System.Int32 e double para System.Double.

Por isso, int e Int32 são exatamente o mesmo tipo. A palavra-chave existe apenas para deixar o código mais curto e legível.

Outro detalhe importante: o valor padrão de todos os tipos numéricos é zero. Assim, um campo numérico de uma classe que você não inicializa começa sempre com 0.

Números inteiros no C#

Os números inteiros no C# aparecem no dia a dia principalmente em três tipos: short, int e long. Como os nomes sugerem, o short serve para valores menores, o int atende à grande maioria dos casos e o long guarda números muito grandes.

Na prática, o int é a escolha padrão. Quando você escreve um literal inteiro como 42, o compilador trata esse valor como int, a menos que outro tipo seja exigido.

A tabela a seguir resume tamanho e faixa de cada tipo inteiro, de acordo com a referência oficial dos tipos numéricos integrais.

Tipo Tamanho Faixa de valores
sbyte 8 bits -128 a 127
byte 8 bits 0 a 255
short 16 bits -32.768 a 32.767
ushort 16 bits 0 a 65.535
int 32 bits cerca de -2,1 bilhões a 2,1 bilhões
uint 32 bits 0 a 4.294.967.295
long 64 bits cerca de -9,2 quintilhões a 9,2 quintilhões
ulong 64 bits 0 a 18.446.744.073.709.551.615

Existem também nint e nuint, cujo tamanho acompanha a plataforma. No entanto, eles servem para cenários de interoperabilidade e você raramente vai precisar deles no código comum.

Tipos unsigned: inteiros sem sinal

Cada tipo inteiro com sinal tem uma versão unsigned, que significa "sem sinal". Enquanto o int aceita valores negativos, o uint aceita apenas zero e valores positivos, e o mesmo vale para long e ulong e para short e ushort.

Em outras palavras, você pode atribuir um valor negativo a um tipo com sinal, mas nunca a um tipo unsigned. Veja o exemplo:

short balance = 10;            // OK
ushort negativeBalance = -10;  // ERRO de compilação: valor fora da faixa do ushort

Na primeira linha, a atribuição funciona normalmente. Já na segunda, o compilador recusa o valor, porque -10 não cabe na faixa de um ushort.

Entre os números no C#, os tipos unsigned ocupam o mesmo espaço em memória que seus pares com sinal. Assim, int e uint ocupam 32 bits, short e ushort ocupam 16 bits, e long e ulong ocupam 64 bits.

A diferença está no uso do bit mais alto. Como o tipo sem sinal não precisa representar negativos, ele dobra o limite positivo.

Mesmo assim, a documentação lembra que muitas aplicações usam int ou long até para valores sempre positivos. Isso acontece porque as APIs do .NET adotam os tipos com sinal como padrão, o que evita conversões desnecessárias.

Números reais: float, double e decimal

Para números reais no C#, os populares "números quebrados", a linguagem também oferece três tipos. O float aloca 32 bits, o double aloca 64 bits e o decimal aloca 128 bits de memória.

Esses tipos não têm versões unsigned. Portanto, todos aceitam valores positivos e negativos desde o início.

Tipo Tamanho Precisão aproximada Sufixo do literal
float 4 bytes 6 a 9 dígitos f ou F
double 8 bytes 15 a 17 dígitos nenhum, d ou D
decimal 16 bytes 28 a 29 dígitos m ou M

Veja como declarar um valor em cada um deles:

double salaryA = 3000.50;
float salaryB = 3000.50f;
decimal salaryC = 3000.50m;

Note que o double recebe o valor diretamente, enquanto o float exige a letra f no fim e o decimal exige a letra m. Isso acontece porque o compilador reconhece qualquer literal com ponto como double, então o sufixo indica que você quer outro tipo.

Se você esquecer o sufixo, o código não compila. Afinal, não existe conversão implícita de double para float nem para decimal.

Quando usar cada tipo de números no C#

A escolha entre os tipos reais depende do problema. A orientação oficial é usar double para cálculos gerais, float quando a memória é limitada e decimal quando você precisa de precisão decimal exata, como em cálculos financeiros.

O motivo é a forma de armazenamento. O float e o double seguem o padrão binário IEEE 754, por isso alguns valores decimais simples, como 0.1, não têm representação exata.

double total = 0.1 + 0.2;
Console.WriteLine(total == 0.3);   // False

decimal exactTotal = 0.1m + 0.2m;
Console.WriteLine(exactTotal == 0.3m); // True

Esse comportamento dos números no C# não é um defeito da linguagem, e sim uma característica da representação binária. Por outro lado, o decimal trabalha em base 10 e minimiza esse tipo de erro de arredondamento.

Outra diferença relevante aparece nas operações inválidas. Com double, uma divisão por zero resulta em infinito ou NaN, enquanto com decimal o .NET lança DivideByZeroException.

Conversão implícita

Ao trabalhar com números no C#, muitas vezes você precisa passar um valor de um tipo para outro. Por padrão, o compilador tenta uma conversão implícita e, se ela não for segura, aponta um erro.

A conversão implícita acontece quando o tipo de destino consegue guardar qualquer valor do tipo de origem. Nesse caso, basta atribuir uma variável à outra:

int baseSalary = 3000;
float salary = 3000.50f;

salary = baseSalary; // conversão implícita de int para float

O inverso não funciona sem ajuda. Por exemplo, atribuir um double a um int gera erro de compilação, porque a parte decimal seria perdida.

As conversões implícitas possíveis entre os tipos numéricos são as seguintes:

  • sbyte: short, int, long, float, double, decimal e nint
  • byte: short, ushort, int, uint, long, ulong, float, double, decimal, nint e nuint
  • short: int, long, float, double, decimal e nint
  • ushort: int, uint, long, ulong, float, double, decimal, nint e nuint
  • int: long, float, double, decimal e nint
  • uint: long, ulong, float, double, decimal e nuint
  • long: float, double e decimal
  • ulong: float, double e decimal
  • char: ushort, int, uint, long, ulong, float, double e decimal
  • float: double

Repare que nenhum tipo converte implicitamente para byte ou sbyte. Da mesma forma, double e decimal não têm conversões implícitas para nenhum outro tipo numérico.

Existe um detalhe sutil nas conversões de números no C#: a conversão de int ou long para float, e de long para double, pode perder precisão. Ainda assim, ela nunca perde a ordem de grandeza do número, e por isso o compilador a aceita sem cast.

Conversão explícita com cast

Na conversão implícita, você apenas associa um valor a outro. Já na conversão explícita, você precisa informar o tipo de destino entre parênteses, antes do valor.

Isso ocorre principalmente quando os tipos de números no C# não são totalmente compatíveis e a conversão pode perder informação. O cast funciona como um aviso no código: quem lê sabe que ali existe um risco.

int valueA = 52;
uint valueB = (uint)valueA; // conversão explícita

double average = 19.75;
int truncated = (int)average; // 19, a parte decimal é descartada

No segundo exemplo, o cast de double para int simplesmente corta a parte decimal. Portanto, não espere arredondamento: o resultado de 19.75 vira 19.

O que acontece com números negativos no cast

Se valueA fosse uma constante negativa, como (uint)-52, o compilador acusaria erro, porque ele consegue avaliar o valor em tempo de compilação. No entanto, quando o valor negativo vem de uma variável, o comportamento muda.

Por padrão, as operações com números inteiros no C# rodam em contexto unchecked, então o valor "dá a volta" e vira um número positivo enorme. Para lançar uma OverflowException nesse caso, use a palavra-chave checked:

int negativeValue = -52;

uint wrapped = (uint)negativeValue;           // 4294967244, sem erro
uint safe = checked((uint)negativeValue);     // lança OverflowException

Dessa forma, você decide se prefere desempenho máximo ou uma falha explícita quando o valor não cabe no tipo de destino.

Converter texto em números no C# com Parse

O método Parse existe em todos os tipos numéricos e converte uma string em número. Ele é muito útil para transformar em números no C# as entradas de usuário, arquivos ou parâmetros recebidos como texto.

int value = int.Parse("25");
decimal price = decimal.Parse("19.90", CultureInfo.InvariantCulture);

Se o texto não representar um número válido, o Parse lança uma exceção em tempo de execução. Normalmente será uma FormatException, ou uma OverflowException quando o número não couber no tipo.

Também vale lembrar que o Parse usa a cultura atual do sistema. Assim, em uma máquina configurada em português, a vírgula é o separador decimal, e por isso o exemplo acima informa CultureInfo.InvariantCulture.

Quando a entrada pode vir errada, prefira TryParse. Ele devolve true ou false em vez de lançar exceção:

if (int.TryParse(userInput, out int quantity))
{
    Console.WriteLine($"Quantity: {quantity}");
}
else
{
    Console.WriteLine("Invalid number.");
}

O balta.io tem um artigo dedicado a esse tema, com mais exemplos de Parse e TryParse no C#.

Classe Convert

A classe Convert permite tentar converter vários tipos de dados, e não apenas strings. Ela oferece métodos como ToInt32, ToDouble e ToDecimal, que aceitam diferentes tipos de origem.

int value = Convert.ToInt32("25");
int fromDouble = Convert.ToInt32(19.75); // 20, arredonda
int fromNull = Convert.ToInt32(null as string); // 0

Repare em duas diferenças importantes na conversão de números no C#, em relação ao cast e ao Parse. Primeiro, ao converter um double, o Convert.ToInt32 arredonda o valor, enquanto o cast apenas trunca.

Além disso, quando recebe uma string nula, o Convert.ToInt32 devolve zero, enquanto o int.Parse lança exceção. Para strings válidas, porém, o Convert.ToInt32 chama o próprio Int32.Parse internamente.

Operadores aritméticos com números no C#

Para fazer contas com números no C#, você usa os operadores aritméticos. Os principais são:

  • Soma: +
  • Subtração: -
  • Multiplicação: *
  • Divisão: /
  • Resto da divisão: %

Assim como na matemática, o C# executa multiplicação e divisão antes de soma e subtração. Se precisar mudar essa ordem, use parênteses.

int a = 2 + 2 * 2;   // 6
int b = 2 + (2 * 2); // 6, mesmo resultado
int c = (2 + 2) * 2; // 8, a soma acontece antes

Os operadores aritméticos funcionam com todos os tipos numéricos que vimos, como short, int, long, float, double e decimal. Contudo, quando os dois operandos são inteiros, o resultado também é inteiro.

int sum = 25 + 22;        // 47
int subtraction = 25 - 22; // 3
int multiplication = 25 * 22; // 550
int division = 22 / 5;    // 4, a parte decimal é descartada
int remainder = 22 % 5;   // 2

Na divisão inteira, o C# trunca o resultado em direção ao zero. Ou seja, 22 / 5 vale 4, e -7 / 2 vale -3.

Para obter o resultado com casas decimais, pelo menos um dos operandos precisa ser real. Por exemplo, 22 / 5.0 resulta em 4.4, e (double)22 / 5 também.

Overflow e divisão por zero

Quando uma conta com números inteiros no C# passa do limite do tipo, ocorre overflow. Em contexto unchecked, que é o padrão, o valor dá a volta silenciosamente.

int max = int.MaxValue;
Console.WriteLine(unchecked(max + 1)); // -2147483648

Já a divisão inteira por zero sempre lança DivideByZeroException. Com double, por sua vez, a mesma operação resulta em Infinity, sem exceção.

Operadores de atribuição

Os operadores de atribuição entram em cena sempre que você precisa guardar valores, inclusive números no C#, em uma variável. O mais básico é o =, mas o C# também oferece formas compostas que combinam conta e atribuição.

int a = 0;  // valor padrão
a += 5;     // mesmo que a = a + 5
a -= 2;     // mesmo que a = a - 2
a *= 3;     // mesmo que a = a * 3
a /= 3;     // mesmo que a = a / 3

Veja que cada linha simplifica a operação descrita no comentário. Com isso, o código fica mais curto sem perder clareza.

Se você só quer somar ou subtrair 1, use os operadores de incremento e decremento:

int a = 0;
a++; // soma 1 ao valor original
a--; // subtrai 1 do valor original

Esses operadores aparecem com frequência em laços for e em contadores de modo geral.

Operadores de comparação

Para comparar números no C#, você usa os operadores de comparação e de igualdade:

  • Igual: ==
  • Diferente: !=
  • Maior que: >
  • Menor que: <
  • Maior ou igual a: >=
  • Menor ou igual a: <=

Lembre sempre que = atribui um valor, enquanto == compara dois valores. Essa confusão é um dos erros mais comuns de quem está começando.

Toda comparação retorna um valor booleano, true ou false. Veja alguns exemplos:

int a = 25;

bool isZero = a == 0;          // false
bool isNotZero = a != 0;       // true
bool isPositive = a > 0;       // true
bool isNegative = a < 0;       // false
bool isAtMostZero = a <= 0;    // false
bool isAtLeastZero = a >= 0;   // true

Na prática, esses resultados alimentam estruturas como if, while e expressões condicionais. Por isso, dominar comparações é essencial para escrever qualquer regra de negócio.

Um cuidado extra com números no C# vale para double e float. Como vimos, 0.1 + 0.2 == 0.3 retorna false, então compare valores reais com uma tolerância ou use decimal quando a exatidão importa.

Boas práticas com números no C#

Depois de conhecer tipos, conversões e operadores, vale reunir algumas recomendações que evitam bugs comuns. Elas valem tanto para projetos pequenos quanto para sistemas maiores.

  • Adote int como padrão para inteiros e long apenas quando o valor puder passar de 2 bilhões.
  • Use decimal para dinheiro, preços e qualquer cálculo financeiro.
  • Reserve double para cálculos científicos, medidas físicas e estatística.
  • Prefira TryParse ao Parse quando a entrada vier do usuário.
  • Use checked em trechos críticos onde um overflow silencioso seria perigoso.
  • Aplique o separador _ para deixar literais grandes mais legíveis, como 1_000_000.

Além disso, evite misturar decimal com double na mesma expressão. O compilador exige conversão explícita nesse caso, justamente porque os dois tipos representam valores de forma diferente.

Se você quer se aprofundar em outros tipos fundamentais, o artigo sobre manipulação de strings em C# e .NET complementa bem este conteúdo.

Conclusão

Os números no C# se dividem entre tipos inteiros e reais, cada um com tamanho, faixa e precisão próprios. Escolher bem entre int, long, double e decimal evita estouros de valor e erros de arredondamento.

Em seguida, entender conversões implícitas, casts, Parse e Convert permite mover valores entre tipos com segurança. Por fim, os operadores aritméticos, de atribuição e de comparação completam a base para qualquer cálculo no .NET.

Para praticar esses conceitos de forma guiada, o curso Fundamentos do C# do balta.io aborda tipos, variáveis e operadores passo a passo.

FAQ

Qual a diferença entre int e long no C#?

O int ocupa 32 bits e guarda valores até cerca de 2,1 bilhões. Já o long ocupa 64 bits e suporta números muito maiores, na casa dos quintilhões.

Por que preciso usar o sufixo m no decimal?

Por padrão, o compilador trata qualquer literal com ponto como double. Como não existe conversão implícita de double para decimal, o sufixo m indica que o literal já é decimal.

Devo usar double ou decimal para valores monetários?

Use decimal, porque ele trabalha em base 10 e minimiza erros de arredondamento. Já o double é mais adequado a cálculos científicos e medidas.

Qual a diferença entre Parse e Convert?

O Parse converte apenas strings e lança exceção para valores nulos. Por sua vez, o Convert aceita vários tipos de origem e devolve zero quando recebe uma string nula.

O que acontece quando divido dois inteiros no C#?

Nesse caso, o resultado é inteiro e a parte decimal é descartada, truncando em direção ao zero. Para obter casas decimais, converta pelo menos um operando para double ou decimal.