C# Matemática (Math)

Os operadores +, -, *, / e % (aula Operadores Aritméticos) fazem as contas do dia a dia. Mas o C# não tem um operador para raiz quadrada, nem para potência (o ^ do C# não é potência!), nem para arredondar. Essas contas ficam na classe Math ("matemática"), a calculadora científica do .NET: Math.Sqrt(81) dá 9, Math.Pow(2, 10) dá 1024, Math.Round(2.7) dá 3. Nesta aula você vai conhecer as funções mais usadas, com a saída real de cada uma, e duas surpresas: o arredondamento que transforma 2,5 em 2, e a raiz cúbica de 27 que não dá exatamente 3.

Você vai aprender
  • As funções básicas: Abs, Max, Min, Pow, Sqrt, Cbrt e as constantes PI e E.
  • Os quatro jeitos de tirar as casas decimais: Round, Floor, Ceiling e Truncate.
  • Por que Math.Round(2.5) dá 2, e como pedir o arredondamento "da escola".
  • Como arredondar dinheiro com decimal e casas decimais.
  • Como limitar um valor com Clamp, e o sinal com Sign.
  • Seno, cosseno e logaritmos, e por que Math.Sin(30) não dá 0,5.
  • Quais funções aceitam int e decimal, e quais só double.
  • Os resultados especiais (NaN, ∞) e os erros reais CS0266, CS1503, CS1501 e OverflowException.

Todos os programas foram rodados com o .NET 10 (dotnet --version = 10.0.112, C# 14) na máquina fictícia da Ana (usuária ana, computador notebook), na pasta ~/cs, com dotnet run arquivo.cs. O notebook está em português, por isso os números com casas decimais saem com vírgula (1,4142…).


A ideia: a calculadora científica

Uma calculadora comum tem as quatro operações. A científica tem uma fileira de teclas a mais: √, xʸ, π, sin, log… Você digita o número, aperta a tecla e lê o resultado.

A classe Math é essa fileira de teclas. Cada tecla é um método: você escreve Math., o nome da tecla e, entre parênteses, o número. O resultado volta para o seu programa, para guardar numa variável ou mostrar. Como a Array da aula A Classe Array, é uma classe estática: não precisa criar nada, é só usar.


1. As funções básicas

Console.WriteLine(Math.Abs(-7));
Console.WriteLine(Math.Max(12, 45));
Console.WriteLine(Math.Min(12, 45));
Console.WriteLine(Math.Pow(2, 10));
Console.WriteLine(Math.Sqrt(81));
Console.WriteLine(Math.Sqrt(2));
Console.WriteLine(Math.Cbrt(27));
Console.WriteLine(Math.PI);
Console.WriteLine(Math.E);
$ dotnet run basico.cs
7
45
12
1024
9
1,4142135623730951
3,0000000000000004       « a raiz cúbica de 27 "quase" 3
3,141592653589793
2,718281828459045
LinhaNome (em inglês)O que faz
Math.Abs(-7)absolute, valor absolutoTira o sinal: −7 vira 7, e 7 continua 7. Útil para "a diferença entre dois números", sem importar qual é maior: Math.Abs(a - b).
Math.Max(12, 45)maximum, máximoO maior dos dois: 45.
Math.Min(12, 45)minimum, mínimoO menor dos dois: 12.
Math.Pow(2, 10)power, potência2 elevado a 10: 2 × 2 × … × 2, dez vezes = 1024. O resultado é double.
Math.Sqrt(81)square root, raiz quadradaO número que, vezes ele mesmo, dá 81: 9.
Math.Sqrt(2)A raiz de 2 não é um número "redondo": 1,4142135623730951 (as casas que cabem num double).
Math.Cbrt(27)cube root, raiz cúbicaO número que, multiplicado três vezes, dá 27. Deveria ser 3, mas veio 3,0000000000000004.
Math.PIπA constante π (pi), a volta do círculo dividida pelo diâmetro. Não tem parênteses: não é uma tecla, é um número guardado.
Math.EeO número de Euler, base dos logaritmos naturais.
Por que 3,0000000000000004?

O double guarda os números em binário, com um número limitado de casas, e a maioria das contas com ele tem um errinho na última casa (aula float, double e decimal). Na maior parte do tempo, o errinho "some" quando o número é mostrado. Na raiz cúbica de 27, no Linux, ele apareceu. No editor do guia, que roda o .NET em WebAssembly, o mesmo Math.Cbrt(27) mostra 3: as duas máquinas fazem a conta por caminhos um pouco diferentes, e as duas estão dentro do que o double promete. Por isso, nunca compare um resultado de Math com ==; arredonde antes (Math.Round(x, 6) == 3) ou confira se a diferença é minúscula (Math.Abs(x - 3) < 0.000001).

O ^ não é potência

Em muitas calculadoras e planilhas, 2^10 é "2 elevado a 10". Em C#, o ^ é outra coisa (o "ou exclusivo" de bits), e 2 ^ 10 dá 8, sem erro nenhum. Para potência, sempre Math.Pow(2, 10). Para elevar ao quadrado, x * x também serve, e é mais rápido.

Missão: num triângulo retângulo com lados 3 e 4, a hipotenusa (o lado maior) é a raiz quadrada de 3² + 4². O código já calcula 3² + 4². Complete com Math.Sqrt para mostrar Hipotenusa: 5.

Hipotenusa: 5
double a = 3, b = 4;
double c = Math.Pow(a, 2) + Math.Pow(b, 2);   // falta a raiz
Console.WriteLine($"Hipotenusa: {c}");
double a = 3, b = 4;
double c = Math.Sqrt(Math.Pow(a, 2) + Math.Pow(b, 2));
Console.WriteLine($"Hipotenusa: {c}");

2. Tirar as casas decimais: Round, Floor, Ceiling, Truncate

São quatro jeitos diferentes de transformar 2,7 num número inteiro, e cada um serve para uma coisa:

double[] valores = [2.3, 2.5, 2.7, -2.5, 3.5];
foreach (double x in valores)
{
    Console.WriteLine($"{x,5}: Round {Math.Round(x),3}  Floor {Math.Floor(x),3}  Ceiling {Math.Ceiling(x),3}  Truncate {Math.Truncate(x),3}");
}
$ dotnet run arredondar.cs
  2,3: Round   2  Floor   2  Ceiling   3  Truncate   2
  2,5: Round   2  Floor   2  Ceiling   3  Truncate   2
  2,7: Round   3  Floor   2  Ceiling   3  Truncate   2
 -2,5: Round  -2  Floor  -3  Ceiling  -2  Truncate  -2
  3,5: Round   4  Floor   3  Ceiling   4  Truncate   3
Math.Round
"Arredondar": vai para o inteiro mais perto. 2,3 → 2; 2,7 → 3. E no meio do caminho, o 2,5? Veja a próxima seção.
Math.Floor
"Chão": sempre para baixo, para o inteiro menor. 2,7 → 2. Com negativos, "para baixo" é para mais longe do zero: −2,5 → −3.
Math.Ceiling
"Teto": sempre para cima. 2,3 → 3. Bom para "quantas caixas eu preciso", em que sobrar um item já exige mais uma caixa.
Math.Truncate
"Truncar", cortar: joga fora as casas decimais, sem arredondar nada. 2,7 → 2; −2,5 → −2. É o que o (int) faz (aula Conversão de Tipos).

Os quatro devolvem um double (ou um decimal, se você passar um decimal), só que sem casas. Para guardar num int, converta: int caixas = (int)Math.Ceiling(x);.

Missão: a Ana vai mandar 23 potes de doce, e cada caixa leva 6. O programa diz Caixas: 3, mas em 3 caixas só cabem 18 potes! Use Math.Ceiling para arredondar para cima e mostrar Caixas: 4. Dica: a divisão tem de ser com casas decimais, senão o 23 / 6 já vira 3 antes do Ceiling.

Caixas: 4
int potes = 23;
int porCaixa = 6;
int caixas = potes / porCaixa;
Console.WriteLine($"Caixas: {caixas}");
int potes = 23;
int porCaixa = 6;
int caixas = (int)Math.Ceiling(potes / (double)porCaixa);
Console.WriteLine($"Caixas: {caixas}");

3. O arredondamento bancário: 2,5 vira 2

Na tabela acima, o Math.Round(2.5) deu 2, e o Math.Round(3.5) deu 4. Não é defeito. Quando o número está exatamente no meio, o Round vai para o inteiro par mais perto: 2,5 → 2 (par), 3,5 → 4 (par). Esse é o arredondamento bancário (ou "do par"). Ele existe porque, numa lista grande de valores, arredondar sempre para cima faria a soma crescer; indo para o par, metade das vezes sobe e metade desce, e a soma fica justa.

Console.WriteLine(Math.Round(2.5));
Console.WriteLine(Math.Round(3.5));
Console.WriteLine(Math.Round(2.5, MidpointRounding.AwayFromZero));
Console.WriteLine(Math.Round(3.14159, 2));
Console.WriteLine(Math.Round(2.665m, 2));
Console.WriteLine(Math.Round(2.665m, 2, MidpointRounding.AwayFromZero));
$ dotnet run bancario.cs
2
4
3
3,14
2,66
2,67
MidpointRounding.AwayFromZero
"No ponto do meio, afaste do zero": o arredondamento que se aprende na escola. 2,5 → 3, e −2,5 → −3. Use quando a regra do seu problema for essa (notas escolares, por exemplo).
Math.Round(3.14159, 2)
O segundo número diz quantas casas decimais manter: 3,14.
Math.Round(2.665m, 2)
Com decimal (o m no fim), a conta é exata. 2,665 está bem no meio entre 2,66 e 2,67, e o bancário escolhe o par: 2,66. Com AwayFromZero, 2,67.
Dinheiro: decimal e uma regra escolhida de propósito

Para preços, use decimal e arredonde só no fim da conta: Math.Round(preco * 0.85m, 2). E escolha a regra de propósito: o padrão (bancário) é o comum em sistemas financeiros; o AwayFromZero é o que muita gente espera ver numa nota fiscal. Só para mostrar (sem mudar o valor guardado), use a formatação: {preco:C} ou {preco:F2} (aula Formatar Números).


4. Limitar e ver o sinal: Clamp e Sign

int[] pedidos = [-5, 3, 12];
foreach (int volume in pedidos)
{
    Console.WriteLine($"{volume,3} → {Math.Clamp(volume, 0, 10)}");
}
Console.WriteLine(Math.Sign(-8));
Console.WriteLine(Math.Sign(0));
Console.WriteLine(Math.Sign(3.5));
$ dotnet run clamp.cs
 -5 → 0
  3 → 3
 12 → 10
-1
0
1
Math.Clamp(volume, 0, 10)
"Prender" o valor entre um mínimo e um máximo: abaixo de 0 vira 0, acima de 10 vira 10, e o que está dentro fica como está. É o volume de um aparelho, que não passa do máximo. Faz o mesmo que Math.Min(Math.Max(volume, 0), 10), só que legível.
Math.Sign(x)
O "sinal": −1 se for negativo, 0 se for zero, 1 se for positivo. Sempre um int, mesmo com 3.5.

5. Seno, cosseno e logaritmos

double graus = 30;
double radianos = graus * Math.PI / 180;
Console.WriteLine(Math.Sin(radianos));
Console.WriteLine(Math.Round(Math.Sin(radianos), 4));
Console.WriteLine(Math.Sin(30));
Console.WriteLine(Math.Log10(1000));
Console.WriteLine(Math.Log(Math.E));
Console.WriteLine(Math.Log2(1024));
$ dotnet run trig.cs
0,49999999999999994
0,5
-0,9880316240928618      « Math.Sin(30): 30 radianos, não 30 graus!
3
1
10
graus * Math.PI / 180
O Sin ("seno"), o Cos ("cosseno") e o Tan ("tangente") medem os ângulos em radianos, não em graus. Uma volta inteira tem 360 graus ou 2π radianos; esta conta converte.
0,49999999999999994
O seno de 30° é 0,5. Saiu "quase" 0,5, pelo mesmo motivo do Cbrt. Arredondado para 4 casas, 0,5.
Math.Sin(30)
Esquecer a conversão não dá erro: dá o seno de 30 radianos (umas 4,8 voltas), que é −0,988.
Math.Log10(1000)
Logaritmo na base 10: "10 elevado a quanto dá 1000?". 3. Ele conta os dígitos de um número: (int)Math.Log10(n) + 1.
Math.Log(x)
Logaritmo natural, na base e. Math.Log(x, b) usa a base b.
Math.Log2(1024)
Na base 2: 10, porque 2¹⁰ = 1024. É quantas vezes dá para dividir por 2, a conta da busca binária da aula A Classe Array.

6. int, double ou decimal?

Algumas funções têm uma versão para cada tipo e devolvem o mesmo tipo que receberam. Outras só existem para double:

FunçõesAceitamDevolvem
Abs, Max, Min, Clamp, Signint, long, double, decimal…o mesmo tipo (o Sign, sempre int)
Round, Floor, Ceiling, Truncatedouble e decimalo mesmo tipo
Pow, Sqrt, Cbrt, Sin, Cos, Log…só double (um int vira double sozinho)double
decimal area = 49m;
double lado = Math.Sqrt((double)area);
Console.WriteLine(lado);
Console.WriteLine(Math.Abs(-19.90m));
Console.WriteLine(Math.Max(4.99m, 6.50m));
Console.WriteLine(Math.Round(16.9915m, 2));
int resultado = (int)Math.Pow(2, 3);
Console.WriteLine(resultado);
$ dotnet run tipos.cs
7
19,90
6,50
16,99
8
Math.Sqrt((double)area)
O Sqrt não aceita decimal: é preciso converter para double antes (sem isso, erro CS1503).
Math.Abs(-19.90m)
Com decimal, o resultado também é decimal e mantém as casas: 19,90.
(int)Math.Pow(2, 3)
O Pow sempre devolve double, mesmo com dois int. Para guardar num int, converta (sem isso, erro CS0266).

Escrever menos: using static

Num programa cheio de contas, repetir Math. cansa. Com using static System.Math; na primeira linha do arquivo, as funções podem ser chamadas pelo nome (aula Namespaces e using):

$ cat staticusing.cs
using static System.Math;
Console.WriteLine(Sqrt(16) + Pow(2, 3) + PI);
$ dotnet run staticusing.cs
15,141592653589793

Sem essa linha, escrever só Sqrt(16) dá error CS0103: O nome "Sqrt" não existe no contexto atual. O guia continua escrevendo Math., que deixa claro de onde a função vem.


7. Resultados especiais: NaN e ∞

Com double, algumas contas impossíveis não dão erro: dão um valor especial.

Console.WriteLine(Math.Sqrt(-1));
Console.WriteLine(Math.Log(0));
Console.WriteLine(1.0 / 0);
Console.WriteLine(double.IsNaN(Math.Sqrt(-1)));
$ dotnet run especiais.cs
NaN
-∞
∞
True
NaN
Not a Number, "não é um número": a raiz de −1 não existe entre os números reais. Qualquer conta com NaN dá NaN, e ele "contamina" tudo o que vem depois.
-∞ e ∞
Infinito negativo e positivo: o logaritmo de 0 e a divisão de um double por zero. (Com int, dividir por zero é exceção: DivideByZeroException.)
double.IsNaN(x)
O jeito certo de perguntar "é NaN?". O x == double.NaN dá sempre false, até quando x é NaN.

Quando dá errado

error CS0266: Não é possível converter implicitamente tipo "double" em "int". Existe uma conversão explícita (há uma conversão ausente?)
$ cat potencia.cs
int resultado = Math.Pow(2, 3);
$ dotnet run potencia.cs
/home/ana/cs/potencia.cs(1,17): error CS0266: Não é possível converter implicitamente tipo "double" 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.

O que significa: o Math.Pow (e o Sqrt, o Round, o Ceiling…) devolve double, e um double não cabe num int sem você pedir, porque poderia perder as casas.

Como consertar: guarde num double, ou converta: int resultado = (int)Math.Pow(2, 3);.

error CS1503: Argumento 1: não é possível converter de "decimal" para "double"
$ cat raiz-decimal.cs
decimal area = 49m;
decimal lado = Math.Sqrt(area);
$ dotnet run raiz-decimal.cs
/home/ana/cs/raiz-decimal.cs(2,26): error CS1503: Argumento 1: não é possível converter de "decimal" para "double"

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

O que significa: o Math.Sqrt só existe para double, e o C# não converte decimal em double sozinho.

Como consertar: double lado = Math.Sqrt((double)area);. Se precisa do resultado em decimal, converta de volta: (decimal)Math.Sqrt((double)area).

error CS1501: Nenhuma sobrecarga para o método "Max" leva 3 argumentos

O que significa: você escreveu Math.Max(3, 9, 5). O Max e o Min comparam dois números de cada vez.

Como consertar: encadeie, Math.Max(3, Math.Max(9, 5)), ou ponha num array e use o LINQ: new[] { 3, 9, 5 }.Max() (aula Agregar).

error CS0103: O nome "Sqrt" não existe no contexto atual

O que significa: faltou o Math. antes do nome da função.

Como consertar: Math.Sqrt(16), ou using static System.Math; na primeira linha.

Unhandled exception. System.OverflowException: Negating the minimum value of a twos complement number is invalid.
$ cat abs-min.cs
int menor = int.MinValue;
Console.WriteLine(menor);
Console.WriteLine(Math.Abs(menor));
$ dotnet run abs-min.cs
-2147483648
Unhandled exception. System.OverflowException: Negating the minimum value of a twos complement number is invalid.
   at Program.<Main>$(String[] args) in /home/ana/cs/abs-min.cs:line 3

O que significa: "inverter o sinal do menor valor não é válido". O menor int é −2.147.483.648, mas o maior é 2.147.483.647: o positivo dele não cabe num int (aula Inteiros). Em vez de dar um número errado, o Math.Abs para o programa (código 134).

Como consertar: se os valores podem chegar perto dos limites, use long: Math.Abs((long)menor).

Resultado estranho, sem erro: Math.Round(2.5) deu 2, Math.Sin(30) deu −0,988

O que significa: o Round usa o arredondamento bancário (o meio vai para o par), e as funções de ângulo usam radianos.

Como consertar: Math.Round(x, MidpointRounding.AwayFromZero) para o arredondamento da escola, e graus * Math.PI / 180 para converter os ângulos.


Referência rápida

MétodoExemploResultado
Math.Abs(x)Math.Abs(-7)7
Math.Max(a, b) / Math.Min(a, b)Math.Max(12, 45)45
Math.Pow(x, y)Math.Pow(2, 10)1024 (double)
Math.Sqrt(x) / Math.Cbrt(x)Math.Sqrt(81)9
Math.Round(x)Math.Round(2.5)2 (bancário)
Math.Round(x, MidpointRounding.AwayFromZero)Math.Round(2.5, …)3
Math.Round(x, casas)Math.Round(3.14159, 2)3,14
Math.Floor(x) / Math.Ceiling(x)Math.Floor(2.7) / Math.Ceiling(2.3)2 / 3
Math.Truncate(x)Math.Truncate(-2.7)−2
Math.Clamp(x, min, max)Math.Clamp(12, 0, 10)10
Math.Sign(x)Math.Sign(-8)−1
Math.Sin / Cos / TanMath.Sin(Math.PI / 2)1 (ângulos em radianos)
Math.Log(x) / Log10 / Log2Math.Log10(1000)3
Math.PI / Math.E3,14159… / 2,71828…

Exercícios

1. Potência

Complete para calcular 5 elevado a 3:

double cubo = Math.(5, 3);

2. Para baixo

Qual método transforma 9,99 em 9 e −9,99 em −10?

Math.(x)

3. Limitar

Complete para a nota ficar sempre entre 0 e 10:

nota = Math.(nota, 0, 10);

4. Quanto vale Math.Round(4.5)?

5. Quanto vale 2 ^ 3 em C#?

6. A Ana precisa de ônibus de 40 lugares para 130 pessoas. Qual conta dá o número certo de ônibus?

Missão final: uma pizza grande ou duas médias?

Missão: na pizzaria, a grande tem 35 cm de diâmetro e a média, 25 cm. O que dá mais pizza: uma grande ou duas médias? A área de um círculo é π × raio², e o raio é metade do diâmetro. O programa usa 3.14 no lugar de π e erra a grande. Troque pelo Math.PI e calcule a área das duas médias juntas. A saída tem de ser Grande: 962,11 cm² e Duas médias: 981,75 cm².

Grande: 962,11 cm²
Duas médias: 981,75 cm²
double grande = 3.14 * (35 / 2.0) * (35 / 2.0);
double duasMedias = 0;   // calcule aqui
Console.WriteLine($"Grande: {grande:F2} cm²");
Console.WriteLine($"Duas médias: {duasMedias:F2} cm²");
double grande = Math.PI * Math.Pow(35 / 2.0, 2);
double duasMedias = 2 * Math.PI * Math.Pow(25 / 2.0, 2);
Console.WriteLine($"Grande: {grande:F2} cm²");
Console.WriteLine($"Duas médias: {duasMedias:F2} cm²");

Surpresa: as duas médias dão um pouco mais de pizza. Com o 3.14, a grande saía 961,62 cm², meio centímetro quadrado a menos: o Math.PI tem muito mais casas (3,141592653589793). O 35 / 2.0, com o .0, garante a divisão com casas decimais (17,5); com 35 / 2, daria 17 e uma pizza menor.


Resumo

  • A classe Math é a calculadora científica do .NET: Math.Abs, Max, Min, Pow, Sqrt, Cbrt, e as constantes Math.PI e Math.E. Potência é Math.Pow, nunca ^.
  • Para tirar as casas: Round (o mais perto), Floor (para baixo), Ceiling (para cima) e Truncate (corta).
  • O Round usa o arredondamento bancário: 2,5 → 2, 3,5 → 4. Para o da escola, MidpointRounding.AwayFromZero. Com dinheiro, decimal e Math.Round(valor, 2) no fim.
  • Clamp prende um valor entre um mínimo e um máximo; Sign dá o sinal.
  • Sin, Cos e Tan usam radianos (graus * Math.PI / 180).
  • Pow, Sqrt e as funções científicas trabalham com double: converta para guardar em int (CS0266) ou para passar um decimal (CS1503). Os resultados podem ter um errinho na última casa, que pode até mudar entre o editor e o Linux: não compare com ==.