È 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