Newsgroup: it.comp.lang.c++
Titolo: Float e rappresentazione approssimata: cosa???!

Saluti al NG!
Leggo sul manuale di C++ di Deitel & Deitel (consigliatomi su questo
NG da Sergio e Maurus, che ringrazio per l'ottimo suggerimento) che la
rappresentazione di un numero tramite float è comunque approssimata, e
che per sapere se due numeri float sono uguali è meglio vedere se la
loro differenza in valore assoluto è minore di un numero abbastanza
piccolo. In altre parole è sconsigliato l'operatore ==. Ma perchè? mi
stupisco perchè un discorso del genere si può fare in fisica, quando
si misura un oggetto: allora non potrò mai sapere la grandezza
*esatta*, ma ma solo quella approssimata in cui l'errore rimane entro
l'imprecisionde dello strumento di misura (ad esempio con un righello
comune posso misurare lunghezze con precisione di massimo mezzo
millimetro). Nel computer, invece, la cosa dovrebbe essere diversa:
infatti tutti i numeri sono espressi da una sequenza di bit 0 o 1;
quindi se ho due float - che se non sbaglio stanno in 32bit - perchè
non li confronto *esattamente* bit per bit con ==?
Grazie
Attilio

Dimmi a che pagina hai letto questa cosa sulle variabili float, che magari
gli ridņ un'occhiata.
Ok?
Saluti,
Sergio
P.S.
Per il suggerimento, grazie di nulla!

"xyz" <attilio...@NOSPAMtiscali.it> wrote in message
news:3e0f2e6...@news.tiscali.it...

La spiegazione e' "fisica" appunto, o meglio matematica.
In teoria si parla di numeri reali e quindi l'uguaglianza fra 2 numeri reali
in teoria funziona sempre.
Ma la rappresentazione di un numero reale su 32 bits, in virgola mobile
dovra' per forza di cose essere accurata fino ad un certo punto, poiche' si
ha il limite appunto del numero di bits col quale rappresentare un certo
valore.
Se poi prendiamo numeri periodici o un numero particolare tipo il pigreco,
la rappresentazione binaria sara' ovviamente imprecisa.
Inoltre rappresentare un numero reale in virgola mobile, vuol dire
rappresentarlo nella forma segno, mantissa, esponente.
Alla fine si tratta sempre di una sequenza di 0 e 1 ma e' un calcolo diverso
che si fa rispetto ai numeri interi.
Fra due numeri rappresentati in virgola mobile su 32 bits ci sono infiniti
numeri reali.
Faccio un esempio senno' il discorso e' fumoso:
#include <iostream>
int main()
{
 float fd = 10.0;
 float fe = fd/3.0;
 if (fd == 3.0*fe)
  std::cout << "Uguali! " << "fd == 3*fe == " << fd << std::endl;
 else
  std::cout << "Diversi! " << "fd = " << fd << " fe = " << fe << " 3*fe = "
<< 3.0*fe << std::endl;
 system("PAUSE");
 return 0;
}
fd e 3*fe sono matematicamente uguali.
Ma il mio programma va nel ramo "Diversi!" pur dando come risultato fd =
3*fe = 10.
Infatti l'output e':
Diversi! fd = 10 fe = 3.33333 3*fe = 10
Press any key to continue . . .
Cio' significa che fe e' stato rappresentato come numero con cifre uguali
dopo la virgola ma che a un certo punto devono arrestarsi essendoci il
limite dei 32 bits di rappresentazione, per cui moltiplicandolo per 3 non
puo' ritornare al valore 10 iniziale per cui il test fallisce.
Poi il fatto che 3*fe venga stampato col valore 10 in effetti non e' chiaro,
forse dipende da come i valori float vengano formattati in fase di cout.
Maurus

"xyz" <attilio...@NOSPAMtiscali.it> wrote

Prova quest'esempio:
#include <iostream>
int main()
{
 double n = 1.0;
 int i;
 for (i=0; i<10; ++i) {
  n /= 3.0;
 }
 for (i=0; i<10; ++i) {
  n *= 3.0;
 }
 std::cout << 1.0 - n << std::endl;
 return 0;
}
Penseresti che dopo aver diviso dieci volte per tre e poi moltiplicato dieci
volte per tre, il risultato resterebbe 1.0. Pertanto, per colpa del'instabilita
numerica, c'e infatti diferenza piccolissima.
Claudio Puviani


Sapresti dirmi come mai se faccio
std::cout << n << std::endl;
Alla fine mi stampa 1 ?
Mi e' venuto questo dubbio.
Maurus

 Mi e' venuto questo dubbio.
E semplicemente questione di formato. Puoi fare uso del manipolatore
'setprecision' per cambiare il comportamento:
std::cout << std::setprecision(4) << n << std::endl;
Esistono altri manipolatori piu o meno utili.
Claudio Puviani

 wrote in message
news:3e0f6351$1...@nopics.sjc...

Aspetta, forse non mi sono spiegato bene.
Anche col setprecision(4) mi stampa 1 quando voglio stampare n alla fine pur
stampando un numerino in virgola mobile quando fai:
std::cout << 1.0 - n << std::endl;
Cio' vuol dire che nel calcolo (1.0 - n) n e' diverso da 1 .
Ma quando vado a stampare n cout me lo formatta a 1.
Ma quale e' questo motivo per cui n viene formattato a 1 ?
Maurus

 Ma quale e' questo motivo per cui n viene formattato a 1 ?
Scusami, mancava una cosa:
std::cout << std::setprecision(4) << std::scientific << n << std::endl;
Claudio Puviani


No, ancora non ho tolto il mio dubbio:
Osserva il programma:
#include <iostream>
#include <iomanip>
int main()
{
 int i;
 double n = 1.0;
 for (i=0; i<10; ++i)
  n /= 3.0;
 for (i=0; i<10; ++i)
  n *= 3.0;
 std::cout << "1.0 - n =" << 1.0 - n << std::endl;
 std::cout << "n = " << std::setprecision(4) << std::scientific << n <<
std::endl;
 system("PAUSE");
 return 0;
}
E l'output che ottengo:
1.0 - n =-2.22045e-016
n = 1.0000e+000
Press any key to continue . . .
Allora la mia domanda e':
come mai nel calcolo (1.0 - n), la n e' diversa da 1.0 ma quando la si
stampa con std::cout e' stampata proprio come 1? (1.000e+000)
Maurus


Wooops!
Arcano risolto:
con
std::cout << "n = " << std::setprecision(16) << std::scientific << n <<
std::endl;
ora l'output e' giustamente:
1.0 - n =-2.22045e-016
n = 1.0000000000000002e+000
Press any key to continue . . .
Dovevo solo settare meglio la precisione.
Grazie per l'aiuto Claudio.
Maurus

gli ridņ un'occhiata.
Della versione italiana: Cap 2 p 67, Buona abitudine 2.12
Attilio

forse dipende da come i valori float vengano formattati in fase di cout.
Ho provato il codice e il risultato è lo stesso ("diversi"). Perciò
faccio un'ipotesi: alla divisione fe prende il valore
3.3-e-tanti-tre-quanti-entrano-in-32bit. Perciò quando moltiplico
nella condizione di if chiedo di verificare l'uguaglianza tra 10.0 e
9.9-e-tanti-nove-quanti-tre-avevo-prima che ovviamente non è
verificata => "diversi".
L'output grafico è 10 questo a causa dell'arrotondamento: leggendo a
pag 66 del Deitel & Deitel mi pare di capire che prima di essere
passato a cout un float viene arrotondato. Per scegliere il numero di
decimali da visualizzare il libro suggerisce:
 cout << "Diversi! " << "fd = " << fd << " fe = " << fe << " 3*fe =
"<< setprecision(2) << setiosflags(ios::fixed | ios::showpoint)
<<3.0*fe << endl;  //il tutto preceduto da #include "iomanip.h"
setprecision specifica il n di decimali, ios fixed è per stampare a
video un num a virgola fissa e showpoint per far vedere il punto e i
decimali che gli stanno dietro. Con la suddetta istruzione il progr
stampa 10.00 perchè arrotonda 9.9-e-tanti-nove-quanti-stanno-in-32bit.
Come capire se il progr *effettua* l'arrotondamento o se, invece, il
numero era già 10? semplice: se moltiplico per 2.0:
cout << "Diversi! " << "fd = " << fd << " fe = " << fe << " 3*fe = "<<
setprecision(2) << setiosflags(ios::fixed | ios::showpoint) <<2.0*fe
<< endl;  //il tutto preceduto da #include "iomanip.h"
ottengo 6.67! significa che in memoria conservo
9.9-e-tanti-nove-quanti-stanno-in-32bit, ma prima di cout arrotondo a
10.00.
Ora: se provo a dichiarare fe e fd come double mi aspetto di avere lo
stesso output... e invece no! l'output è "uguali" e non riesco a
capire perchè! Come in fisica faccio due ipotesi:
1) che l'operatore == all'interno di if si comporti in modo strano con
i double e consideri 10.0 uguale a 9.999 con una sfilza di nove dietro
2) che la differenza tra float e double non sia solo il *numero* di
bit usati per contenere un numero reale (che è di 48 o 64 per il
double), ma anche il *modo*
Tuttavia nessuna delle ipotesi mi soddisfa. Chi mi aiuta a capire?
Attilio

 Attilio
Riprendiamo il solito programmino modificato e vediamo se qualcuno sbroglia
la faccenda:
#include <iostream>
#include <iomanip>
int main()
{
// sostituire con float nell'altro caso
 double fd = 10.0;
 double fe = fd/3.0;
 if (fd == 3.0*fe)
 {
 std::cout << "Uguali! " << std::setprecision(16) << std::scientific << "fd
== 3*fe == " << fd << " fe = " << fe << std::endl;
 std::cout << std::setprecision(16) << std::scientific << "2*fe = " <<
2.0*fe  << std::endl;
 }
 else
  std::cout << "Diversi! " << std::setprecision(16) << std::scientific <<
"fd = " << fd << " fe = " << fe << " 3*fe = " << 3.0*fe << std::endl;
 system("PAUSE");
 return 0;
}
/*
*************************** Output con i float:
********************************
Diversi! fd = 1.0000000000000000e+001 fe = 3.3333332538604736e+000 3*fe =
9.9999
997615814209e+000
Press any key to continue . . .
****************************************************************************
****
*/
/*
**************************** Output con i double:
******************************
Uguali! fd == 3*fe == 1.0000000000000000e+001 fe = 3.3333333333333335e+000
2*fe = 6.6666666666666670e+000
Press any key to continue . . .
****************************************************************************
****
*/
Ora il mio dubbio e' questo:
come mai, nel caso di double si ha che fd = 3*fe?
Eppure vediamo sopra che fd = 10  fe = fe = 3.3333333333333335
2*fe = 6.6666666666666670
Per cui se sommiamo (con la calcolatrice di windows) fe a 2*fe otterremo
3*fe = 10.0000000000000005 che e' diverso da fd.
L'affare si ingrossa!
Maurus

>L'affare si ingrossa!
Hai proprio ragione! e guarda un po' qua:
parto dalle mie due ipotesi:
1) che l'operatore == all'interno di if si comporti in modo strano con
i double e consideri 10.0 uguale a 9.999 con una sfilza di nove dietro
2) che la differenza tra float e double non sia solo il *numero* di
bit usati per contenere un numero reale (che è di 48 o 64 per il
double), ma anche il *modo*
Ora cerco di fare un esperimento che dia ragione ad una escludendo
l'altra:
#include "stdafx.h"
#include "iostream.h"
int main(int argc, char* argv[])
{
	if (10.0 == 9.999999999999999)
		cout << "Uguali\n";
	else
		cout << "diversi\n";
	return 0;
}
output= diversi. E fin qui ci siamo.
Ma basta aggiungere anche solo un 9 dopo quelli che ho gia scritto
e... output=UGUALI!!! sembra che il limite sia il 15° decimale: dal
16° in poi approssima e i valori sono uguali. Già, ma questo risultato
serve a poco: il dubbio rimane: *chi* approssima? l'operatore "==" in
esecuzione, oppure è il compilatore che prima di scrivere il binario
eseguibile mette il 9.9-ecc(con 16 decimali) in un double e quindi
approssima a 10 già nella compilazione?
Attilio

 Attilio
Ma no, l'operatore == non dovrebbe essere coinvolto in questa faccenda.
E' tutta una questione di rappresentazione binaria.
Il fatto che alla 16 cifra decimale 9.99...9 sia visto uguale al valore 10.0
per i double e' perche' il double riesce a ben rappresentare i numeri con
una precisione di 10 elevato alla -15 e un numero piu' piccolo verra' quindi
approssimato.
Per cui aggiungendo un 9 alla 16 cifra decimale hai fatto si' che il tuo
numero venga rappresentato in binario proprio come il valore 10.0
Questo discorso per i float verra' a galla prima appunto perche' i float
hanno meno bit a disposizione.
Quindi per tornare alla somma che non mi tornava prima:
fd = 10.0  fe = fe = 3.3333333333333335 (16 cifre decimali)
2*fe = 6.6666666666666670 (16 cifre decimali)
Per cui se sommiamo (con la calcolatrice di windows) fe a 2*fe otterremo
3*fe = 10.0000000000000005 che e' diverso da fd.
Ok e' diverso da fd ma solo considerando la 16a cifra decimale che e' un 5.
Ammettendo che i double sono rappresentabili fino a 15 cifre decimali, ne
converremo che fd = 10.000000000000000 -> 5 tagliato fuori
Per cui si avra' una bellissima e matematica uguaglianza!
Maurus