Determinare le Caratteristiche dei Numeri in Virgola Mobile in Linguaggio C
Per chi scrive algoritmi e programmi di calcolo numerico in linguaggio C, è fondamentale conoscere le caratteristiche dei numeri in virgola mobile supportati dal compilatore utilizzato. Queste caratteristiche includono la precisione, l'intervallo di valori rappresentabili e il comportamento delle operazioni aritmetiche.
Sebbene allo sviluppatore comune spesso non interessino questi dettagli, per chi si occupa di calcolo numerico essi sono cruciali per garantire l'accuratezza e l'affidabilità dei risultati ottenuti.
Prima di addentrarci nelle funzionalità matematiche offerte dal linguaggio C e dalla sua libreria standard, in questa lezione vedremo come determinare le caratteristiche dei numeri in virgola mobile supportati dal compilatore C in uso.
- Per determinare se un compilatore C supporta i numeri in virgola mobile, possiamo verificare la presenza della macro
__STDC_IEC_559__. - Le caratteristiche specifiche dei numeri in virgola mobile, come la precisione e l'intervallo di valori rappresentabili, possono essere ottenute tramite le macro definite nel file di intestazione
<float.h>. - Il comportamento della valutazione delle espressioni in virgola mobile può essere determinato utilizzando la macro
FLT_EVAL_METHOD, che indica il metodo di valutazione adottato dal compilatore.
Determinare se un compilatore C supporta i numeri in virgola mobile
Quando abbiamo studiato i numeri in virgola mobile abbiamo visto i tipi che il linguaggio C mette a disposizione per rappresentare questi numeri: float, double e long double. Ognuno di questi tipi ha delle caratteristiche specifiche in termini di precisione e intervallo di valori rappresentabili.
Il fatto è che lo standard del linguaggio C prevede due cose:
-
Non è necessario che il compilatore implementi tutti e tre i tipi in virgola mobile. Un compilatore potrebbe non supportarli affatto.
Si pensi, ad esempio, ai compilatori C per i microcontrollori più semplici (ad esempio Microchip PIC18, Atmel AVR e STMicroelectronics STM32), che spesso non supportano i numeri in virgola mobile per motivi di spazio e prestazioni. Anzi, nella maggioranza dei casi, tali microcontrollori non dispongono di un'unità di calcolo in virgola mobile (FPU, Floating Point Unit).
In questi casi, quello che accade è che il compilatore semplicemente non supporta i numeri in virgola mobile e non compila il codice che li utilizza, oppure emula le operazioni in virgola mobile tramite codice software, il che può essere molto lento.
-
Non è mandatorio che, seppure i tipi in virgola mobile siano supportati, essi debbano rispettare lo standard IEEE 754.
Lo standard IEEE 754 definisce come devono essere rappresentati i numeri in virgola mobile, quali operazioni devono essere supportate e come devono comportarsi in caso di errori (come overflow, underflow, divisione per zero, ecc.). Tuttavia, un compilatore C potrebbe scegliere di implementare i numeri in virgola mobile in modo diverso.
In generale, non esiste un modo universale per sapere se un compilatore C supporta i numeri in virgola mobile. Tuttavia, è possibile almeno verificare che essi rispettino lo standard IEEE 754 (chiamato anche standard IEC 60559) utilizzando la macro __STDC_IEC_559__.
Ad esempio, il seguente codice verifica se il compilatore supporta lo standard IEEE 754:
#include <stdio.h>
int main() {
#ifdef __STDC_IEC_559__
printf("Il compilatore supporta lo standard IEEE 754.\n");
#else
printf("Il compilatore NON supporta lo standard IEEE 754.\n");
#endif
return 0;
}
Determinare le caratteristiche dei numeri in virgola mobile
Detto questo, una volta che abbiamo stabilito che il compilatore supporta i numeri in virgola mobile, è utile conoscere le loro caratteristiche specifiche, come la precisione e l'intervallo di valori rappresentabili. Per fare ciò, possiamo utilizzare il file di intestazione <float.h>, che definisce una serie di macro che forniscono queste informazioni.
Queste macro hanno uno di tre possibili prefissi, a seconda del tipo di virgola mobile a cui si riferiscono:
FLT_per il tipofloatDBL_per il tipodoubleLDBL_per il tipolong double
Nella tabbella seguente sono elencate alcune delle macro più comuni definite in <float.h>:
| Macro | Descrizione |
|---|---|
FLT_RADIX |
Specifica la base della rappresentazione dell'esponente (tipicamente 2 per i numeri binari). Questa macro è comune a tutti i tipi in virgola mobile. |
FLT_MANT_DIG, DBL_MANT_DIG, LDBL_MANT_DIG |
Numero di cifre significative (bit) nella mantissa per float, double e long double, rispettivamente. Questo numero indica il numero di cifre nella base specificata da FLT_RADIX che possono essere rappresentate con precisione e non in base 10. |
FLT_MIN_EXP, DBL_MIN_EXP, LDBL_MIN_EXP |
L'esponente minimo (in base FLT_RADIX) per i tipi float, double e long double, rispettivamente. |
FLT_MAX_EXP, DBL_MAX_EXP, LDBL_MAX_EXP |
L'esponente massimo (in base FLT_RADIX) per i tipi float, double e long double, rispettivamente. |
FLT_MIN, DBL_MIN, LDBL_MIN |
Il più piccolo numero positivo normalizzato rappresentabile per i tipi float, double e long double, rispettivamente. |
FLT_MAX, DBL_MAX, LDBL_MAX |
Il più grande numero rappresentabile per i tipi float, double e long double, rispettivamente. |
FLT_EPSILON, DBL_EPSILON, LDBL_EPSILON |
Il cosiddetto epsilon di macchina, che rappresenta la differenza più piccola tra 1 e il successivo numero rappresentabile per i tipi float, double e long double, rispettivamente. |
FLT_DIG, DBL_DIG, LDBL_DIG |
Numero di cifre decimali che possono essere rappresentate con precisione per i tipi float, double e long double, rispettivamente. |
Tra le macro elencate, alcune sono particolarmente utili per comprendere la precisione e l'intervallo dei numeri in virgola mobile:
FLT_MANT_DIG,DBL_MANT_DIG,LDBL_MANT_DIG: Queste macro indicano quante cifre significative possono essere rappresentate con precisione. Ad esempio, seFLT_MANT_DIGè 24, significa che un numero di tipofloatpuò rappresentare con precisione fino a 24 cifre binarie nella sua mantissa.FLT_EPSILON,DBL_EPSILON,LDBL_EPSILON: Rappresenta il limite superiore dell'errore relativo dovuto all'arrotondamento quando si eseguono operazioni in virgola mobile. Ad esempio, seFLT_EPSILONè 1.19209290e-07, significa che la differenza tra 1 e il successivo numero rappresentabile è circa 1.19 x 10^-7 per i numeri di tipofloat.
Proviamo a scrivere un semplice programma che stampa alcune di queste caratteristiche per i tipi float, double e long double:
#include <stdio.h>
#include <float.h>
int main() {
printf("Caratteristiche dei numeri in virgola mobile:\n\n");
printf("Tipo float:\n");
printf(" Base dell'esponente: %d\n", FLT_RADIX);
printf(" Cifre significative: %d\n", FLT_MANT_DIG);
printf(" Min esponente: %d\n", FLT_MIN_EXP);
printf(" Max esponente: %d\n", FLT_MAX_EXP);
printf(" Min positivo normalizzato: %e\n", FLT_MIN);
printf(" Max rappresentabile: %e\n", FLT_MAX);
printf(" Epsilon di macchina: %e\n", FLT_EPSILON);
printf(" Cifre decimali rappresentabili con precisione: %d\n\n", FLT_DIG);
printf("Tipo double:\n");
printf(" Base dell'esponente: %d\n", FLT_RADIX);
printf(" Cifre significative: %d\n", DBL_MANT_DIG);
printf(" Min esponente: %d\n", DBL_MIN_EXP);
printf(" Max esponente: %d\n", DBL_MAX_EXP);
printf(" Min positivo normalizzato: %e\n", DBL_MIN);
printf(" Max rappresentabile: %e\n", DBL_MAX);
printf(" Epsilon di macchina: %e\n", DBL_EPSILON);
printf(" Cifre decimali rappresentabili con precisione: %d\n\n", DBL_DIG);
printf("Tipo long double:\n");
printf(" Base dell'esponente: %d\n", FLT_RADIX);
printf(" Cifre significative: %d\n", LDBL_MANT_DIG);
printf(" Min esponente: %d\n", LDBL_MIN_EXP);
printf(" Max esponente: %d\n", LDBL_MAX_EXP);
printf(" Min positivo normalizzato: %Le\n", LDBL_MIN);
printf(" Max rappresentabile: %Le\n", LDBL_MAX);
printf(" Epsilon di macchina: %Le\n", LDBL_EPSILON);
printf(" Cifre decimali rappresentabili con precisione: %d\n\n", LDBL_DIG);
return 0;
}
Provando a compilare ed eseguire questo programma, otterremo un output che ci fornisce una panoramica delle caratteristiche dei numeri in virgola mobile supportati dal nostro compilatore C. Ad esempio, su un sistema linux con GCC che gira su una macchina con architettura x86_64, l'output potrebbe essere simile al seguente:
Caratteristiche dei numeri in virgola mobile:
Tipo float:
Base dell'esponente: 2
Cifre significative: 24
Min esponente: -125
Max esponente: 128
Min positivo normalizzato: 1.175494e-38
Max rappresentabile: 3.402823e+38
Epsilon di macchina: 1.192093e-07
Cifre decimali rappresentabili con precisione: 6
Tipo double:
Base dell'esponente: 2
Cifre significative: 53
Min esponente: -1021
Max esponente: 1024
Min positivo normalizzato: 2.225074e-308
Max rappresentabile: 1.797693e+308
Epsilon di macchina: 2.220446e-16
Cifre decimali rappresentabili con precisione: 15
Tipo long double:
Base dell'esponente: 2
Cifre significative: 64
Min esponente: -16381
Max esponente: 16384
Min positivo normalizzato: 3.362103e-4932
Max rappresentabile: 1.189731e+4932
Epsilon di macchina: 1.084202e-19
Cifre decimali rappresentabili con precisione: 18
Comportamento della Valutazione in Virgola Mobile
Un dettaglio fondamentale da considerare quando si lavora con i numeri in virgola mobile in C è il comportamento della valutazione delle espressioni.
Infatti, lo standard del linguaggio C consente ai compilatori di utilizzare una precisione maggiore durante la valutazione delle espressioni in virgola mobile rispetto a quella dei tipi di dati coinvolti. Questo significa che, ad esempio, un'espressione che coinvolge variabili di tipo float potrebbe essere valutata con una precisione equivalente a quella di un double o addirittura di un long double. Questo comportamento può portare a risultati inattesi, specialmente quando si confrontano i risultati di operazioni in virgola mobile con valori attesi.
Per chi sviluppa algoritmi di calcolo numerico, è importante essere consapevoli di questo aspetto. Pertanto, lo standard C fornisce un'ulteriore macro, FLT_EVAL_METHOD, definita in <float.h>, che indica il metodo di valutazione utilizzato dal compilatore:
0: Le espressioni in virgola mobile sono valutate con la precisione del tipo di dato coinvolto.1: Le espressioni in virgola mobile sono valutate con la precisione didouble.2: Le espressioni in virgola mobile sono valutate con la precisione dilong double.-1: Il metodo di valutazione non è specificato.
Ad esempio, possiamo scrivere un semplice programma per stampare il valore di FLT_EVAL_METHOD:
#include <stdio.h>
#include <float.h>
int main() {
printf("Metodo di valutazione delle espressioni in virgola mobile: %d\n",
FLT_EVAL_METHOD);
return 0;
}
Compilando ed eseguendo questo programma, otterremo un output che ci indica il metodo di valutazione delle espressioni in virgola mobile utilizzato dal nostro compilatore C. Ad esempio, su un sistema linux con GCC 15 che gira su una macchina con architettura x86_64, l'output potrebbe essere:
Metodo di valutazione delle espressioni in virgola mobile: 0
Il che vuol dire che le espressioni in virgola mobile sono valutate con la precisione del tipo di dato coinvolto.