È possibile dubitare dei calcoli di un computer ?
Un'introduzione
La specifica IEEE 754 rappresenta lo standard per il calcolo in virgola mobile, cioè quello usato per i calcoli di tipo scientifico e ingegneristico; a mio parere è un'ottimo esempio di come l'industria informatica e lo sviluppo di software sia basato su un'illusione fondamentale dell'umanità: quella di poter raggiungere e manipolare l'infinito (infinitamente grande, infinitamente piccolo, non importa). Non fraintendiamoci, non voglio assolutamente sminuire il valore di questo standard (fra l'altro utilizzato ormai da tutti i progettisti di processori e linguaggi di programmazione), ma far capire il dolore che si prova di fronte a risultati come questi: Calcolo a 32 bit:
1.93 - 0.93 = **0.99999994**
Calcolo a 64 bit:
1.93 - 0.93 = **0.9999999999999999**
Ci aspetteremmo un bel 1.0, ma la dannatissima Macchina per Calcolare sembra aver perso qualche rotella ! Ma non la usano mica per calcolare le previsioni del tempo, le traiettorie dei missili, gli interessi del conto corrente ? Com'è possibile che dopo anni e anni di errori nessuno se ne sia accorto ?
La Dura Verità
In verità lo standard IEEE 754 definisce come ottenere la migliore approssimazione possibile di numeri decimali (quelli con la virgola), e i calcoli mostrati sopra aderiscono perfettamente a questo standard. Paradossalmente in un calcolo a 64 bit 0.9999999999999999 è un'approssimazione aritmeticamente migliore di 1 per il calcolo "1.93 - 0.93" ! I numeri decimali (in matematica sono chiamati Numeri Reali) infatti non possono essere rappresentati da un numero finito di cifre, in quanto potenzialmente possono avere infinite cifre decimali. E qui entra in gioco l'approssimazione usata dalle Macchine per Calcolare:
- con 64 bit si possono rappresentare 2^64 valori (18.446.744.073.709.551.616 poco più di diciotto miliardi di miliardi)
- 1 bit è usato per il segno +/-
- 11 bit sono usati per l' esponente
- 52 bit sono utilizzati per la cosiddetta mantissa
Esempi
Vediamo ora un esempio utilizzando il linguaggio Java. Per mostrare la differenza fra un numero reale e un numero in floating point a 32 bit ci serviremo di 2 diversi tipi di dati:
- float: un tipo primitivo di Java che rappresenta un valore in floating point la cui codifica rispetta la specifica IEEE 754 a 32 bit
- java.math.BigDecimal: la classe BigDecimal consente di rappresentare un numero decimale con un numero "arbitrario" di cifre decimali (in realtà non più di 2^32, ovvero quattro miliardi e rotti).
Il programma assegna alla variabile float a il valore 0.1 e affida alla variabile BigDecimal b la rappresentazione "reale" di a; siccome a è un'approssimazione a 32 bit di 0.1, b mette in luce la ridicola approssimazione a cui è stato sottoposto il valore "0.1", cioè "0.100000001490116119384765625" ! Moltiplicando ora per valori sempre più grandi poi si può capire la soddisfazione che deve provare Madre Natura a vederci correre come dei matti dietro a numeri sempre più lunghi ma mai abbastanza precisi...
package megadix;
import java.math.*;
/** Programma che dimostra i limiti del calcolo in virgola mobile in Java
* @author De Franciscis Dimitri megadix@yahoo.it */
public class MadNumbers {
private static void display(float a, BigDecimal b) {
System.out.println("--------------------------------------------------");
System.out.println("a = " + a);
System.out.println("b = " + b);
String stringVal = Float.toString(a);
System.out.println("b - a = " + b.subtract(new BigDecimal(stringVal)));
}
public static void main(String[] args) {
System.out.println("Variabili");
System.out.println("a : numero in virgola mobile a 32 bit");
System.out.println("b : rappresentazione con precisione arbitraria di a");
float a = 0.1f;
BigDecimal b = new BigDecimal(a);
display(a, b);
display(a * 1000, b.multiply(new BigDecimal(1000)));
display(a * 1000000, b.multiply(new BigDecimal(1000000)));
display(a * 1000000000, b.multiply(new BigDecimal(1000000000)));
}
}
Output del programma
Variabili
a : numero in virgola mobile a 32 bit
b : rappresentazione con precisione arbitraria di a
--------------------------------------------------
a = 0.1
b = 0.100000001490116119384765625
b - a = 0.000000001490116119384765625
--------------------------------------------------
a = 100.0
b = 100.000001490116119384765625000
b - a = 0.000001490116119384765625000
--------------------------------------------------
a = 100000.0
b = 100000.001490116119384765625000000
b - a = 0.001490116119384765625000000
--------------------------------------------------
a = 1.0E8
b = 100000001.490116119384765625000000000
b - a = 1.490116119384765625000000000