La Libreria Matematica Standard in Linguaggio C

Concetti Chiave
  • La libreria matematica standard in linguaggio C è accessibile includendo l'header <math.h>.
  • La libreria fornisce una vasta gamma di funzioni matematiche comuni, come potenze, radici quadrate, funzioni trigonometriche, logaritmi e altro ancora.
  • Molti compilatori richiedono di collegare esplicitamente la libreria matematica durante la fase di linking, utilizzando l'opzione -lm.
  • La libreria matematica standard segue lo standard IEEE 754 per la rappresentazione dei numeri in virgola mobile, che definisce concetti come zero positivo/negativo, numeri denormalizzati, valori speciali (NaN, infinito) e modalità di arrotondamento.
  • La libreria matematica standard gestisce gli errori matematici come overflow, underflow, divisione per zero e operazioni non definite, spesso utilizzando la variabile globale errno per segnalare tali errori.

Uso della libreria matematica standard

Lo standard del linguaggio C definisce una libreria matematica standard, accessibile includendo l'header <math.h>, che fornisce una serie di funzioni matematiche comuni, come il calcolo di potenze, radici quadrate, funzioni trigonometriche, logaritmi e altro ancora.

Per poterla adoperare, è necessario includere l'header <math.h> all'inizio del programma. Inoltre, molti compilatori richiedono di collegare esplicitamente la libreria matematica durante la fase di linking, utilizzando l'opzione -lm (ad esempio, con GCC: gcc -o programma programma.c -lm).

Alcuni dettagli sullo standard IEEE 754 o IEC 60559

Abbiamo visto in precedenza che, in molte implementazioni del linguaggio C, i tipi di dati in virgola mobile (float e double) seguono lo standard IEEE 754 (noto anche come IEC 60559), che definisce la rappresentazione e il comportamento dei numeri in virgola mobile.

Questo standard specifica, appunto, come rappresentare tali numeri: usando 32 bit per il tipo float (single precision) e 64 bit per il tipo double (double precision).

I numeri vengono memorizzati in notazione scientifica binaria, che consiste in un segno, un esponente e una mantissa (o frazione).

Per comprendere appieno alcuni comportamenti delle funzioni matematiche in C, è utile conoscere alcuni concetti chiave dello standard IEEE 754:

  • Zero Positivo e Zero Negativo:

    Lo standard IEEE 754 rappresenta il segno dei numeri in virgola mobile con un bit dedicato. La conseguenza è che, secondo lo standard, esistono due rappresentazioni distinte dello zero: lo zero positivo (+0) e lo zero negativo (−0).

    Il fatto che lo zero abbia due rappresentazioni può influenzare il comportamento di alcune operazioni matematiche, come la divisione per zero o il calcolo di funzioni che coinvolgono lo zero.

  • Numeri denormalizzati o subnormali:

    Quando si effettua un'operazione che produce un risultato molto piccolo, al di sotto del minimo rappresentabile per il tipo di dato in virgola mobile, si ha una condizione che prende il nome di underflow.

    Per comprendere meglio questo concetto, si consideri il caso in cui, adoperando una calcolatrice, dividiamo ripetutamente un numero per 10. Ad un certo punto, il risultato diventerà così piccolo che la calcolatrice non sarà più in grado di rappresentarlo correttamente, e mostrerà semplicemente "0".

    Questo fenomeno può avere grosse ripercussioni nei calcoli numerici, poiché i risultati di alcune operazioni potrebbero essere approssimati a zero, portando a errori significativi nei calcoli successivi.

    Per evitare questo problema, lo standard IEEE 754 definisce i numeri denormalizzati (o subnormali), che permettono di rappresentare numeri molto piccoli, anche se con una precisione ridotta. Infatti, normalmente i numeri in virgola mobile sono rappresentati con una mantissa normalizzata, ossia significa che la mantissa ha un bit implicito che è sempre 1. Nei numeri denormalizzati, invece, questo bit implicito non è presente, permettendo di rappresentare numeri più piccoli, ma con meno precisione man mano che il numero diventa più piccolo.

  • Valori speciali:

    Lo standard IEEE 754 definisce anche alcuni valori speciali per rappresentare situazioni particolari nei calcoli in virgola mobile:

    • Not a Number (NaN): Rappresenta un valore non numerico, che può risultare da operazioni matematiche non definite, come 0/0 o la radice quadrata di un numero negativo.
    • Infinito Positivo e Infinito Negativo: Rappresentano valori che superano il massimo rappresentabile per il tipo di dato in virgola mobile. Ad esempio, il risultato di 1.0 / 0.0 è l'infinito positivo. Mentre -1.0 / 0.0 è l'infinito negativo.

    Tali valori speciali sono considerati numeri validi a tutti gli effetti e possono essere utilizzati nelle operazioni matematiche, seguendo regole specifiche. Ad esempio:

    • Dividere un numero finito per l'infinito restituisce sempre zero.
    • Qualsiasi operazione aritmetica che coinvolge NaN restituisce sempre NaN.
  • Direzione di Arrotondamento:

    Quando un risultato non può essere rappresentato esattamente con un numero in virgola mobile (ad esempio, quando si vuole rappresentare un numero irrazionale come \pi oppure \sqrt{2}), esso verrà rappresentato con un'approssimazione che dipende dalla direzione di arrotondamento o modalità di arrotondamento.

    Vi sono quattro modalità di arrotondamento definite dallo standard IEEE 754:

    1. Arrotondamento verso il più vicino o round to nearest: il valore viene arrotondato al numero rappresentabile più vicino. In caso di equidistanza, viene scelto il numero con la parte frazionaria pari (round half to even).
    2. Arrotondamento verso zero o round toward zero: il valore viene arrotondato verso il numero rappresentabile più vicino a zero.
    3. Arrotondamento verso l'infinito positivo o round toward +∞: il valore viene arrotondato verso il numero rappresentabile più grande.
    4. Arrotondamento verso l'infinito negativo o round toward -∞: il valore viene arrotondato verso il numero rappresentabile più piccolo.

    Tipicamente, la modalità di arrotondamento predefinita è l'arrotondamento verso il più vicino: round to nearest.

  • Gestione degli errori matematici:

    Lo standard IEEE 754 definisce cinque tipi di errori che possono verificarsi durante le operazioni in virgola mobile:

    1. Overflow: Si verifica quando il risultato di un'operazione è troppo grande per essere rappresentato nel tipo di dato in virgola mobile.
    2. Underflow: Si verifica quando il risultato di un'operazione è troppo piccolo per essere rappresentato nel tipo di dato in virgola mobile.
    3. Divisione per zero: Si verifica quando si tenta di dividere un numero finito per zero.
    4. Operazione non definita: Si verifica quando si esegue un'operazione matematica non definita, come la radice quadrata di un numero negativo.
    5. Inexact: Si verifica quando il risultato di un'operazione non può essere rappresentato esattamente e deve essere arrotondato.

    La libreria matematica standard del C segnala questi errori in modi diversi, che vedremo più avanti in questa lezione.

Tipi e Macro

La libreria matematica standard definisce alcuni tipi di dati e macro utili per lavorare con i numeri in virgola mobile:

  • Tipi di dati:

    • float_t: Un tipo di dato in virgola mobile che rappresenta il tipo di dato con la precisione più alta tra float e double supportata dall'implementazione.
    • double_t: Un tipo di dato in virgola mobile che rappresenta il tipo di dato con la precisione più alta tra double e long double supportata dall'implementazione.
  • Macro:

    • INFINITY: Rappresenta un valore di infinito positivo per il tipo float.
    • NAN: Rappresenta un valore di "Not a Number" (NaN) per il tipo float.

Gestione degli errori nella libreria matematica

Le funzioni definite nella libreria matematica standard gestiscono gli errori in un modo diverso rispetto alle altre funzioni di libreria.

Quando si verifica un errore matematico (ad esempio, il calcolo della radice quadrata di un numero negativo), queste funzioni impostano la variabile globale errno a un valore specifico che indica il tipo di errore verificatosi, invece di restituire un codice di errore direttamente.

Inoltre, quando il valore di ritorno di una funzione è troppo grande per essere rappresentato nel tipo di dato previsto, viene restituito un valore speciale HUGE_VAL, HUGE_VALF o HUGE_VALL, a seconda del tipo di dato (rispettivamente double, float o long double), e errno viene impostato a ERANGE per indicare un errore di overflow.

Queste macro sono di tipo double, float e long double e rappresentano un valore numerico molto grande che indica che il risultato è fuori dall'intervallo rappresentabile. Anzi, spesso questi valori rappresentano l'infinito positivo secondo lo standard IEEE 754 per la rappresentazione dei numeri in virgola mobile.

Le funzioni matematiche nella libreria <math.h> rilevano due tipi principali di errori:

  • Errori di dominio: Si verificano quando l'input fornito a una funzione è al di fuori del dominio valido per quella funzione. Ad esempio, calcolare la radice quadrata di un numero negativo, oppure il logaritmo di un numero non positivo.

    In questi casi, errno viene impostato a EDOM. In alcune implementazione, inoltre, la funzione restituisce NAN (Not a Number) per indicare che il risultato non è definito.

  • Errori di intervallo: Si verificano quando il risultato di una funzione è troppo grande (overflow) o troppo piccolo (underflow) per essere rappresentato nel tipo di dato previsto.

    In questi casi, errno viene impostato a ERANGE, e la funzione restituisce HUGE_VAL, HUGE_VALF o HUGE_VALL per indicare un overflow, oppure 0 per indicare un underflow.

Meccanismi alternativi di segnalazione degli errori matematici

A partire dallo standard C99, la libreria matematica standard offre dei meccanismi alternativi per la segnalazione degli errori, utilizzando delle macro apposite.

In generale, un'implementazione della libreria matematica può segnalare errori in una di queste tre modalità:

  1. Modalità classica: le funzioni impostano errno in caso di errore.
  2. Modalità di eccezione del sistema: le funzioni generano delle eccezioni hardware in caso di errore (ad esempio, divisione per zero). Tali eccezioni vengono, poi, gestite dal sistema operativo o dall'ambiente di esecuzione.
  3. Modalità ibrida: le funzioni utilizzano sia errno che le eccezioni hardware per segnalare errori.

Per capire quale modalità è attualmente adoperata dalla libreria, un programma può utilizzare la macro math_errhandling, definita nell'header <math.h>. Questa macro può assumere i seguenti valori:

Il valore di math_errhandling è determinato in fase di compilazione e dipende dall'implementazione della libreria matematica standard fornita con il compilatore. Non può essere modificato a runtime.

Per verificare il valore di math_errhandling, possiamo scrivere un semplice programma come il seguente:

#include <stdio.h>
#include <math.h>

int main() {
    if (math_errhandling & MATH_ERRNO)
        printf("La libreria matematica utilizza errno per la segnalazione degli errori.\n");

    if (math_errhandling & MATH_ERREXCEPT)
        printf("La libreria matematica utilizza eccezioni hardware per la segnalazione degli errori.\n");

    return 0;
}

Nell'esempio sopra, abbiamo usato l'operatore AND bit a bit (&) per verificare se ciascuno dei due meccanismi di segnalazione degli errori è attivo nella libreria matematica in uso.

Per capire come funzionano questi meccanismi di segnalazione nello standard C99, vediamo come si comportano in caso di overflow e underflow.

  • In caso di overflow, le funzioni matematiche si comportano come segue:

    1. Se il risultato è un infinito esatto, come ad esempio quando si calcola il logaritmo di zero \log(0), la funzione restituisce il valore HUGE_VAL, HUGE_VALF o HUGE_VALL (a seconda del tipo di dato del valore di ritorno). Inoltre, il risultato avrà il segno appropriato (positivo o negativo) in base al calcolo effettuato.
    2. Se il valore di math_errhandling include MATH_ERRNO, la funzione potrebbe impostare errno a ERANGE per indicare l'overflow.
    3. Se il valore di math_errhandling include MATH_ERREXCEPT, la funzione potrebbe generare un'eccezione hardware di overflow. In particolare, viene sollevata un'eccezione di divisione per zero se il risultato è un infinito esatto, altrimenti viene sollevata un'eccezione di overflow floating-point.
  • Se il risultato di una funzione matematica è troppo piccolo per essere rappresentato (underflow), le funzioni si comportano come segue:

    1. La funzione restituisce un valore la cui ampiezza è inferiore al minimo rappresentabile per il tipo di dato del valore di ritorno. In pratica, questo valore viene arrotondato a zero.
    2. Se il valore di math_errhandling include MATH_ERRNO, la funzione potrebbe impostare errno a ERANGE per indicare l'underflow.
    3. Se il valore di math_errhandling include MATH_ERREXCEPT, la funzione potrebbe generare un'eccezione hardware di underflow floating-point.

Da notare che nella descrizione dei comportamenti sopra riportati, le parole potrebbe impostare e potrebbe generare indicano che l'implementazione della libreria matematica standard può scegliere se adottare o meno tali comportamenti in base alla piattaforma e al compilatore in uso.