Il linguaggio C di base ti dà + - * / e %, e nient'altro: nessun operatore di potenza, nessuna radice quadrata, nessun arrotondamento. Tutto ciò che va oltre l'aritmetica sta nella libreria matematica standard, dichiarata in <math.h>.
Queste funzioni lavorano tutte in precisione double: accettano argomenti double e restituiscono double. È la prima cosa da interiorizzare, perché passare un int funziona senza problemi (viene convertito), mentre assegnare il risultato a un int scarta in silenzio la parte decimale.
Per iniziare, e il flag -lm
Sul tuo computer, compilarlo potrebbe fallire:
/usr/bin/ld: /tmp/ccXYZ.o: in function `main':
program.c:(.text+0x1a): undefined reference to `sqrt'
collect2: error: ld returned 1 exit status
È l'inciampo più comune in assoluto con math.h, e non è un errore nel tuo codice. L'header ha fornito le dichiarazioni, quindi il compilatore era soddisfatto; le implementazioni stanno in una libreria separata (libm) che su Linux il linker non include di default. Aggiungi -lm:
gcc program.c -o program -lm
Il flag deve venire dopo i file sorgente: i linker elaborano i loro input in ordine, e una libreria elencata prima del codice che ne ha bisogno non risolve nulla. Su macOS e con MinGW su Windows, le routine matematiche fanno già parte della libreria C standard, quindi -lm non serve (anche se su macOS non fa danni).
Potenze e radici
sqrt(x) di un numero negativo restituisce NaN invece di andare in crash; controlla l'argomento se può essere negativo. hypot vale la pena conoscerla perché calcola lo stesso valore di sqrt(x*x + y*y) senza andare in overflow quando x o y sono molto grandi.
Per piccole potenze intere, la semplice moltiplicazione è più veloce ed esatta: x * x batte pow(x, 2.0), che passa per i logaritmi e può restituire 8.999999999999998 dove ti aspettavi 9.
Arrotondamento: floor, ceil, round, trunc
Quattro funzioni, quattro risposte diverse per i numeri negativi. Questa tabella è la parte da imparare a memoria:
floor(x): il più grande intero non maggiore dix. Si sposta sempre verso meno infinito:floor(-2.3)vale-3.ceil(x): il più piccolo intero non minore dix. Si sposta sempre verso più infinito:ceil(-2.3)vale-2.round(x): l'intero più vicino, con le metà arrotondate lontano dallo zero:round(2.5)vale3,round(-2.5)vale-3.trunc(x): taglia la parte decimale, spostandosi verso lo zero:trunc(-2.7)vale-2.
Tutte e quattro restituiscono un double. Fare il cast a int quando vuoi un intero va bene, ma nota che (int)x da solo esegue un troncamento, quindi (int)(x + 0.5) è il vecchio idioma per "arrotondare", ed è sbagliato per i numeri negativi. Usa round().
fabs e fmod
fabs è il valore assoluto per la virgola mobile. Usare abs al suo posto è un errore classico, perché abs prende un int e la conversione avviene in silenzio:
% non funziona affatto sui double: è un operatore intero e il compilatore lo rifiuta. fmod(a, b) è la sua controparte in virgola mobile, e mantiene il segno di a.
fabs è anche il modo corretto di confrontare due double, perché == sulla virgola mobile è una trappola:
0.1 e 0.2 non si possono rappresentare esattamente in binario, quindi la loro somma è a un soffio da 0.3. Confronta con una tolleranza, mai con ==.
Trigonometria
Le funzioni trigonometriche lavorano in radianti, non in gradi: è l'altra fonte sicura di risposte sbagliate.
L'insieme completo: sin, cos, tan, le loro inverse asin, acos, atan, la versione a due argomenti atan2(y, x) e la famiglia iperbolica sinh, cosh, tanh. Per gli angoli preferisci atan2(y, x) ad atan(y / x): gestisce x == 0 e trova il quadrante giusto.
math.h definisce M_PI sulla maggior parte dei sistemi, ma è un'estensione POSIX e non C standard, quindi può mancare con -std=c17. Definire una tua costante, come sopra, è portabile.
Logaritmi ed esponenziali
Nota la trappola nei nomi ereditata dalla matematica: log() è il logaritmo naturale (base e), non in base 10. La base 10 è log10().
log(0.0) restituisce meno infinito e log(-1.0) restituisce NaN: nessuno dei due va in crash, il che significa che un argomento sbagliato attraversa in silenzio i tuoi calcoli finché qualcosa non stampa nan.
INFINITY, NAN e come riconoscerli
La virgola mobile ha valori che non sono numeri, e math.h dà loro un nome:
L'ultima riga è quella fondamentale. Non puoi scrivere if (x == NAN): è sempre falso. Usa isnan(x). (L'identità x != x è vera solo per NaN, ed è così che spesso viene implementata isnan, oltre a essere una comoda soluzione di riserva se mai incontri una toolchain che non ce l'ha.)
Gli infiniti arrivano dall'overflow (exp(1000.0)) e dalla divisione per zero di un double diverso da zero, che, a differenza della divisione intera per zero, è definita e non va in crash.
Varianti float e long double
Ogni funzione ha tre forme: la versione double, una versione float con il suffisso f e una versione long double con il suffisso l.
float sqrtf(float x);
double sqrt(double x);
long double sqrtl(long double x);
Usa le versioni double normali a meno che tu non abbia un motivo misurato per non farlo: double è il tipo a cui il C promuove di default, e mescolare le varianti float di solito costa più in conversioni di quanto faccia risparmiare. L'unico caso in cui il suffisso conta è quando un calcolo solo float è critico per le prestazioni su hardware senza supporto per i double.
Un esempio pratico
Mettendo insieme diverse di queste funzioni: la distanza tra due punti e le radici di un'equazione di secondo grado:
Nota fabs(disc) < 1e-12 invece di disc == 0.0: la stessa regola della tolleranza di prima, applicata dove un discriminante calcolato da valori misurati non cadrà quasi mai esattamente sullo zero.
<math.h> è un header di una cassetta degli attrezzi più ampia; la pagina sulla libreria standard descrive il resto, e i numeri casuali trattano rand, che sta in <stdlib.h> e non qui.
Domande frequenti
Come si usano le funzioni matematiche in C?
Aggiungi #include <math.h> all'inizio del file e chiamale: sqrt(16.0), pow(2.0, 10.0), fabs(-3.5). Accettano e restituiscono double. Su Linux devi anche collegare la libreria matematica con -lm: gcc program.c -o program -lm.
Perché compilando ottengo 'undefined reference to sqrt'?
L'header ha dato al compilatore la dichiarazione, ma l'implementazione sta in una libreria separata che su Linux il linker non include automaticamente. Aggiungi -lm alla fine del comando: gcc program.c -o program -lm. Su macOS e con MinGW su Windows il codice matematico fa parte della libreria C standard, quindi il flag non serve.
Qual è la differenza tra abs e fabs in C?
abs() di <stdlib.h> prende un int e restituisce un int. fabs() di <math.h> prende un double e restituisce un double. Chiamare abs(-3.7) converte prima l'argomento in int e dà 3, scartando in silenzio la parte decimale: usa fabs per i valori in virgola mobile.
Come si controlla se un valore è NaN in C?
Usa isnan(x) di <math.h>. Non puoi fare il test con x == NAN, perché NaN risulta diverso da tutto, compreso se stesso: questa stranezza è proprio il test di riserva, dato che x != x è vero solo per NaN. Usa isinf(x) per gli infiniti e isfinite(x) per verificare che sia un numero normale.