C++ I numeri in virgola mobile sono strani


Esempio

Il primo errore commesso da quasi ogni programmatore presume che questo codice funzioni come previsto:

float total = 0;
for(float a = 0; a != 2; a += 0.01f) {
    total += a;
}

Il programmatore principiante assume che questo sommerà ogni singolo numero compreso tra 0, 0.01, 0.02, 0.03, ..., 1.97, 1.98, 1.99 , per ottenere il risultato 199 -la risposta matematicamente corretta.

Accadono due cose che rendono falso questo:

  1. Il programma come scritto non conclude mai. a mai diventa uguale a 2 e il ciclo non termina mai.
  2. Se riscriviamo la logica del loop per verificare a < 2 , invece, il ciclo termina, ma il totale finisce per essere qualcosa di diverso da 199 . Sulle macchine compatibili con IEEE754, spesso si sommano invece a circa 201 .

La ragione per cui questo accade è che i numeri in virgola mobile rappresentano approssimazioni dei loro valori assegnati .

L'esempio classico è il seguente calcolo:

double a = 0.1;
double b = 0.2;
double c = 0.3;
if(a + b == c)
    //This never prints on IEEE754-compliant machines
    std::cout << "This Computer is Magic!" << std::endl; 
else
    std::cout << "This Computer is pretty normal, all things considered." << std::endl;

Sebbene ciò che noi programmatori vediamo sono tre numeri scritti in base10, ciò che vedono il compilatore (e l'hardware sottostante) sono numeri binari. Dato che 0.1 , 0.2 e 0.3 richiedono una perfetta divisione di 10 - che è abbastanza facile in un sistema di base 10, ma impossibile in un sistema di base 2 - questi numeri devono essere memorizzati in formati imprecisi, simili a come il numero 1/3 deve essere memorizzato nella forma imprecisa 0.333333333333333... in base-10.

//64-bit floats have 53 digits of precision, including the whole-number-part.
double a =     0011111110111001100110011001100110011001100110011001100110011010; //imperfect representation of 0.1
double b =     0011111111001001100110011001100110011001100110011001100110011010; //imperfect representation of 0.2
double c =     0011111111010011001100110011001100110011001100110011001100110011; //imperfect representation of 0.3
double a + b = 0011111111010011001100110011001100110011001100110011001100110100; //Note that this is not quite equal to the "canonical" 0.3!