O núcleo da linguagem C te dá + - * / e %, e nada mais - nenhum operador de potência, nenhuma raiz quadrada, nenhum arredondamento. Tudo além da aritmética vive na biblioteca matemática padrão, declarada em <math.h>.
Essas funções todas trabalham em precisão double: recebem argumentos double e devolvem double. Essa é a primeira coisa a internalizar, porque passar um int funciona silenciosamente (ele é convertido), enquanto atribuir o resultado a um int descarta a fração em silêncio.
Começando, e a flag -lm
Na sua própria máquina, compilar isso pode falhar:
/usr/bin/ld: /tmp/ccXYZ.o: in function `main':
program.c:(.text+0x1a): undefined reference to `sqrt'
collect2: error: ld returned 1 exit status
Este é de longe o tropeço mais comum com math.h, e não é um erro no seu código. O cabeçalho forneceu as declarações, então o compilador ficou satisfeito; as implementações vivem em uma biblioteca separada (libm) que o linker não inclui por padrão no Linux. Acrescente -lm:
gcc program.c -o program -lm
A flag tem que vir depois dos arquivos-fonte - linkers processam suas entradas em ordem, e uma biblioteca listada antes do código que precisa dela não resolve nada. No macOS e com o MinGW no Windows, as rotinas matemáticas já fazem parte da biblioteca padrão do C, então -lm é desnecessária (embora inofensiva no macOS).
Potências e raízes
sqrt(x) de um número negativo devolve NaN em vez de travar; proteja o argumento se ele puder ser negativo. hypot vale a pena conhecer porque calcula o mesmo valor que sqrt(x*x + y*y) sem estourar quando x ou y é muito grande.
Para potências inteiras pequenas, a multiplicação simples é mais rápida e exata - x * x ganha de pow(x, 2.0), que passa por logaritmos e pode devolver 8.999999999999998 onde você esperava 9.
Arredondamento: floor, ceil, round, trunc
Quatro funções, quatro respostas diferentes para números negativos. Essa tabela é a parte que vale memorizar:
floor(x)- o maior inteiro não maior quex. Sempre se move para o infinito negativo:floor(-2.3)é-3.ceil(x)- o menor inteiro não menor quex. Sempre se move para o infinito positivo:ceil(-2.3)é-2.round(x)- o inteiro mais próximo, com os meios se afastando do zero:round(2.5)é3,round(-2.5)é-3.trunc(x)- corta a parte fracionária, movendo-se em direção ao zero:trunc(-2.7)é-2.
Todas as quatro devolvem um double. Converter para int quando você quer um inteiro está ok, mas note que (int)x sozinho faz truncamento - então (int)(x + 0.5) é o antigo idioma de "arredondar", e ele está errado para números negativos. Use round().
fabs e fmod
fabs é o valor absoluto para ponto flutuante. Recorrer ao abs em vez dele é um erro clássico, porque abs recebe um int e a conversão acontece silenciosamente:
% não funciona com double de jeito nenhum - é um operador inteiro e o compilador o rejeita. fmod(a, b) é seu equivalente em ponto flutuante, e ele mantém o sinal de a.
fabs também é o jeito correto de comparar dois doubles, já que == em ponto flutuante é uma armadilha:
0.1 e 0.2 não podem ser representados exatamente em binário, então a soma deles fica a um fio de 0.3. Compare com uma tolerância, nunca com ==.
Trigonometria
As funções trigonométricas trabalham em radianos, não em graus - a outra fonte confiável de respostas erradas.
O conjunto completo: sin, cos, tan, suas inversas asin, acos, atan, a atan2(y, x) de dois argumentos, e a família hiperbólica sinh, cosh, tanh. Prefira atan2(y, x) a atan(y / x) para ângulos - ela lida com x == 0 e acerta o quadrante.
A math.h define M_PI na maioria dos sistemas, mas isso é uma extensão POSIX e não C padrão, então ela pode estar ausente sob -std=c17. Definir sua própria constante, como acima, é portável.
Logaritmos e exponenciais
Note a armadilha de nomenclatura herdada da matemática: log() é o logaritmo natural (base e), não base 10. Base 10 é log10().
log(0.0) devolve infinito negativo e log(-1.0) devolve NaN - nenhum dos dois trava, o que significa que um argumento ruim viaja silenciosamente pelo seu cálculo até algo imprimir nan.
INFINITY, NAN e como verificá-los
O ponto flutuante tem valores que não são números, e a math.h lhes dá nomes:
Aquela última linha é a crucial. Você não pode escrever if (x == NAN) - é sempre falso. Use isnan(x). (A identidade x != x só é verdadeira para NaN, que é como isnan costuma ser implementada e uma alternativa prática se você algum dia topar com uma cadeia de ferramentas sem ela.)
Infinitos chegam por estouro (exp(1000.0)) e pela divisão de um double diferente de zero por zero, que - ao contrário da divisão inteira por zero - é definida e não trava.
Variantes float e long double
Toda função tem três formas: a versão double, uma versão float com sufixo f e uma versão long double com sufixo l.
float sqrtf(float x);
double sqrt(double x);
long double sqrtl(long double x);
Use as versões double simples a menos que você tenha uma razão medida para não fazê-lo - double é o tipo para o qual o C promove por padrão, e misturar variantes float normalmente custa mais em conversões do que economiza. O único lugar onde o sufixo importa é quando um cálculo só em float é crítico para o desempenho em hardware sem suporte a double.
Um exemplo prático
Juntando várias dessas - a distância entre dois pontos e as raízes de uma equação do segundo grau:
Note o fabs(disc) < 1e-12 em vez de disc == 0.0 - a mesma regra de tolerância de antes, aplicada onde um discriminante calculado a partir de valores medidos quase nunca cairá exatamente em zero.
<math.h> é um cabeçalho dentro de um kit bem maior; a página da biblioteca padrão mapeia o resto, e números aleatórios cobre rand, que vive em <stdlib.h> e não aqui.
Perguntas frequentes
Como uso funções matemáticas em C?
Acrescente #include <math.h> no topo do arquivo e chame-as: sqrt(16.0), pow(2.0, 10.0), fabs(-3.5). Elas recebem e devolvem double. No Linux você também precisa ligar a biblioteca matemática com -lm: gcc program.c -o program -lm.
Por que recebo 'undefined reference to sqrt' ao compilar?
O cabeçalho deu ao compilador a declaração, mas a implementação vive em uma biblioteca separada que o linker não inclui automaticamente no Linux. Acrescente -lm no fim do comando: gcc program.c -o program -lm. No macOS e com o MinGW no Windows, o código matemático faz parte da biblioteca padrão do C, então nenhuma flag é necessária.
Qual é a diferença entre abs e fabs em C?
abs(), de <stdlib.h>, recebe um int e devolve um int. fabs(), de <math.h>, recebe um double e devolve um double. Chamar abs(-3.7) converte o argumento para int primeiro e dá 3, descartando a fração em silêncio - use fabs para valores de ponto flutuante.
Como verifico NaN em C?
Use isnan(x), de <math.h>. Você não pode testar com x == NAN, porque NaN compara como diferente de tudo, inclusive de si mesmo - essa esquisitice é na verdade o teste alternativo: x != x só é verdadeiro para NaN. Use isinf(x) para infinitos e isfinite(x) para checar se é um número comum.