C++
Aritmetica in virgola mobile
Ricerca…
I numeri in virgola mobile sono strani
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:
- Il programma come scritto non conclude mai.
a
mai diventa uguale a2
e il ciclo non termina mai. - Se riscriviamo la logica del loop per verificare
a < 2
, invece, il ciclo termina, ma il totale finisce per essere qualcosa di diverso da199
. Sulle macchine compatibili con IEEE754, spesso si sommano invece a circa201
.
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!