1. Introduzione
Sembra che se il computer è "intelligente", allora 0.1 + 0.2 dovrebbe essere semplicemente 0.3. Ma non è proprio così. Vediamo insieme con un esempio semplice.
double x = 0.1;
double y = 0.2;
double sum = x + y;
Console.WriteLine(sum); // Cosa stampa il programma?
Ora prova a confrontare con 0.3:
Console.WriteLine(sum == 0.3); // Qui sarà true o false?
Se vedi False, non sorprenderti!
Il motivo sta nella rappresentazione dei numeri in memoria
I computer lavorano con i numeri in binario. Ma non tutte le frazioni decimali possono essere rappresentate come frazioni binarie finite, proprio come 1/3 non può essere scritto esattamente come frazione decimale (0.333...). Per esempio, 0.1 in binario è una frazione infinita, e bisogna "arrotondarla" per salvarla.
In parole povere, double a volte "fa finta" di salvare il tuo numero esattamente, ma in realtà salva solo un valore molto vicino.
2. Quali "stranezze" succedono con l'aritmetica dei double?
Vediamo qualche esempio pratico.
Esempio 1. La classica "magia" di 0.1 + 0.2
double a = 0.1;
double b = 0.2;
double sum = a + b;
Console.WriteLine(sum); // 0.30000000000000004
Console.WriteLine(sum == 0.3); // False
Il computer non ha stampato 0.3, ma 0.30000000000000004. La differenza è piccola, ma se lavori, per esempio, con i soldi — diventa un problema serio.
Esempio 2. Somma iterativa
double result = 0;
for (int i = 0; i < 10; i++)
{
result += 0.1;
}
Console.WriteLine(result); // 0.9999999999999999
Volevi 1.0 — hai ottenuto un po' meno. Di nuovo, il motivo è l'arrotondamento interno di double.
Perché è importante nei casi reali
Molti pensano: "Che importa, è solo un piccolo errore, chi se ne frega!" Vediamo un esempio dal mondo dei pagamenti.
Supponiamo che la tua banca online sommi 100 transazioni da 0.1 euro. Se il tuo programma "perde" un centomillesimo di euro ad ogni iterazione, su scala bancaria hai già "perso" soldi veri. E qui il contabile verrà subito da te e chiederà: "Dove sono i nostri soldi?!"
3. Come confrontare correttamente i numeri floating point
Dato che double spesso non può salvare esattamente il valore che ti aspetti, il confronto diretto con == può "fregarti". Invece, si confronta il valore assoluto della differenza con un numero molto piccolo (epsilon).
Esempio di confronto con tolleranza
double a = 0.1 + 0.2;
double b = 0.3;
double epsilon = 0.000001;
if (Math.Abs(a - b) < epsilon)
{
Console.WriteLine("Quasi uguale!"); // Così è più sicuro confrontare
}
Qui diciamo: "Se la differenza tra i numeri è meno di un milionesimo, li consideriamo uguali".
4. Valori speciali di double: Infinity, NaN, -Infinity
Il tipo double non salva solo numeri, ma anche valori speciali. Succedono in situazioni che un matematico vieterebbe a uno studente normale.
Infinito (Infinity)
Cosa succede se dividi 1 per 0?
double result = 1.0 / 0.0;
Console.WriteLine(result); // Infinity
In C# (e in molti linguaggi) la divisione per 0 per double non lancia eccezioni! Invece il risultato diventa il valore speciale "infinito positivo".
Meno infinito (-Infinity)
Se dividi un numero negativo per 0, ottieni meno infinito:
double result = -1.0 / 0.0;
Console.WriteLine(result); // -Infinity
"Non un numero" (NaN — Not a Number)
Se fai qualcosa di davvero strano, tipo calcolare la radice quadrata di un numero negativo:
double result = Math.Sqrt(-1);
Console.WriteLine(result); // NaN
Oppure il risultato di 0.0 / 0.0:
double result = 0.0 / 0.0;
Console.WriteLine(result); // NaN
NaN — è "tutto ciò che non sarebbe un numero nella vita reale".
Come controllare i valori speciali
In C# ci sono funzioni per controllare i valori speciali:
Console.WriteLine(double.IsInfinity(result)); // true se infinito
Console.WriteLine(double.IsNaN(result)); // true se NaN
Tabella: Come double reagisce alle operazioni strane
| Operazione | Risultato | Cosa salva double |
|---|---|---|
|
Infinity | +∞ |
|
-Infinity | -∞ |
|
NaN | Non un numero |
|
NaN | Non un numero |
GO TO FULL VERSION