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 nintbyte: short, ushort, int, uint, long, ulong, float, double, decimal, nint e nuintshort: int, long, float, double, decimal e nintushort: int, uint, long, ulong, float, double, decimal, nint e nuintint: long, float, double, decimal e nintuint: long, ulong, float, double, decimal e nuintlong: float, double e decimalulong: float, double e decimalchar: ushort, int, uint, long, ulong, float, double e decimalfloat: 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
intcomo padrão para inteiros elongapenas quando o valor puder passar de 2 bilhões. - Use
decimalpara dinheiro, preços e qualquer cálculo financeiro. - Reserve
doublepara cálculos científicos, medidas físicas e estatística. - Prefira
TryParseaoParsequando a entrada vier do usuário. - Use
checkedem trechos críticos onde um overflow silencioso seria perigoso. - Aplique o separador
_para deixar literais grandes mais legíveis, como1_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.