Menu

Funzioni matematiche in C: math.h, sqrt, pow e il linking con -lm

Un giro in math.h: sqrt, pow, fabs, floor e ceil, round, fmod, le famiglie trigonometriche e dei logaritmi, INFINITY e NAN, più il flag del linker -lm che causa il classico errore undefined reference.

Questa pagina include editor eseguibili: modifica, esegui e vedi subito l'output.

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 di x. Si sposta sempre verso meno infinito: floor(-2.3) vale -3.
  • ceil(x): il più piccolo intero non minore di x. 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) vale 3, 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.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA