PROBLEMA C++: Float ingannevoli

Pubblicità

Gabryy.

Utente Attivo
Messaggi
359
Reazioni
76
Punteggio
46
Avevo scoperto mesi fa che le variabili float ingannano, nel senso che se per esempio gli assegno 10, in realtà la variabile float non contiene un 10 pulito, ma un 10.001234 (numeri decimali messi a casaccio ma voglio rendere l'idea). Non riesco a spiegarmi bene infatti vi allego un banalissimo codice che conferma quello che ho appena detto:
C++:
#include <iostream>

using namespace std;

int main()
{
   float a = 7.30;   //Se metto double funziona
   float b = 12.30;
   bool risposta;

   risposta = a < 7.30;
   cout << risposta << endl;
   risposta = a > 12.30;
   cout << risposta << endl;
   risposta = b < 7.30;
   cout << risposta << endl;
   risposta = b > 12.30;   //Se metto 12.00001 funziona
   cout << risposta << endl;
}

il 12.30 dentro un float non è un 12.30 ma un 12.30 e qualcosa..
Qualcuno riesce a spiegarmi il perché di questa cosa e qual era il sito per verificare il reale valore di un float? grazie
 
Ricorda che solo le potenze di due sono rappresentate esattamente, tutti gli altri numeri vengono approssimati.
I float vanno bene per memorizzare numeri con poche cifre significative, tutte le operazioni matematiche vanno fatte con double.
 
Non so se non hai capito e se fai finta di non capire: devi studiare la rappresentazione binaria dei numeri in virgola mobile e le relative regole di conversione, non c'è modo di semplificare. In decimale, la frazione 1/10 è 0,1 mentre in binario è un numero periodico (non esiste nessuna rappresentazione finita di 0 e 1 nel sistema binario che rappresenta il "banalissimo" decimale finito 0,1).
Con un generico float ci sono regole di conversione un po più complicate.
In ogni caso, quando si programma non bisogna mai usare gli operatori relazionali allo stesso modo dei numeri interi;
per esempio, se vuoi verificare che un certo numero sia "uguale" a 0.7 devi prima stabilire un margine di errore ragionevole, che chiamiamo epsilon (eps) per esempio
eps = 0.000000001 (un miliardesimo)
mentre, per il confronto NON si fa
if(a==0.7) // probabile errore
ma
if(a>0.7-eps && a<0.7+eps) // cioè se 0.7-eps < a < 0.7+eps
i float non vanno bene per questo tipo di operazioni, servono i double
 
era esattamente il link che volevo sottporre anch'io! solo che con lo schifo di connessione che mi ritrovo non sono riuscito a trovarlo con leprime 2 prove
 
Penso di sapere cosa accada nel tuo programma.
Ne pubblico qui una versione simplificata:

C++:
int main()
{
   float a = 7.30; // qui 7.30 e' un double!
   bool risposta;

   risposta = (a == 7.30);  // confronto tra un float e un double!
   cout << risposta << endl;  // False
}

In C/C++/C#, il tipo per default per numeri a virgola mobile e' double. Infatti in C# l'istruzione di assegnazione ndel codice sopra da' errore dicendo che non puo' assegnare un double (7.3) a un float (a) a meno di non fare una conversione specifica (casting).
A questo punto in a NON hai il valore 7.3 perche' perdi risoluzione (cifre decimali) in quanto 7.3 non e' rappresentabile esattamente come potenze di due (come lo sarebbe per esempio 8.5, ossia 2 alla terza piu' 1/2)
Quindi l'operazione di confronto ritorna False.
Per ovviare l'inconveniente devi usare sempre e solo lo stesso tipo. Ossia, o dichiari la variabile a come double (come hai visto tu stesso) o converti 7.30 in float (con un cast per esempio).

La situazione diventa piu' complicata quando fai operazioni matematiche in successione. Se usi float, per ogni operazione "perdi" cifre significative, e quindi risoluzione. In questo caso, usare sempre e solo double (di fatto il default in C)

La domanda obbligo a questo punto e' "perche' mai esiste il formato float se non e' preciso"

Per lo stesso motivo per cui per i numeri interi esistono i tipi char, byte, int, long, int64 e via dicendo. Un programmatore deve sapere queste cose, e usare il tipo di dato che serve ai nostri scopi. Quando c'e' problema di memoria (per esempio se hai una matrice multidimensionale) occorre quindi stare bene attenti a usare il tipo giusto, un double occupera' sempre il doppio della memoria di un float, mentre un int64 e' otto volte un byte.
Quindi fare tutte le operazioni in double, e solo alla fine convertire e memorizzare il risultato in float.

Eccoti la risposta lunga :)
 
.
Scusate l’intromissione ma è un thread molto intrigante.
Se invece di 0.7 (che è implicitamente 0.7D, ovvero un double) si confrontasse con 0.7F (che invece è un float) non si dovrebbero avere risultati corretti?
si, se la variabile 'a' è un float
Comunque l'idea di confrontare due numeri guardando se il modulo della loro differenza sia inferiore alla risoluzione voluta è una pratica convalidata.
 
ho un altro problema simile, ma con i double e int. L'ultimo risultato mi dà 53 e non 54.
C++:
#include <iostream>

using namespace std;

int main()
{
   double a = 17.54;  //Se metto 17.55 funziona
   int b = a;
   cout << b << endl;  //17
   double c = a - b;
   cout << c << endl;  //0.54
   c *= 100;
   cout << c << endl;  //54
   int d = c;  //Se metto float funziona
   cout << d;  //53. perché 53 e non 54?
}
 
ho un altro problema simile, ma con i double e int. L'ultimo risultato mi dà 53 e non 54.
C++:
#include <iostream>

using namespace std;

int main()
{
   double a = 17.54;  //Se metto 17.55 funziona
   int b = a;
   cout << b << endl;  //17
   double c = a - b;
   cout << c << endl;  //0.54
   c *= 100;
   cout << c << endl;  //54
   int d = c;  //Se metto float funziona
   cout << d;  //53. perché 53 e non 54?
}
Quando tu fai
Codice:
float x;
int y = x;
y non è l'arrotondamento di x a intero, ma la troncatura alla virgola (equivalente a un floor).
Detto questo, quel 54 che hai alla fine, è in realtà 53,9999... e qualcosa, per cui quando lo converti a intero diventa 53.
Poi ti inganni perchè cout invece arrotonda correttamente per cui quel 53,999... lo vedi proprio 54.
Prova così:
C++:
int main()
{
    cout.precision(17);
    double a = 17.54;
    int b = a;
    cout << b << endl;
    double c = a - b;
    cout << fixed << c << endl;
    c *= 100;
    cout << fixed << c << endl;
    int d = c;
    cout << d << endl;
    return 0;
}
l'output sarà
17
0.53999999999999915
53.99999999999991473
53
Se invece metti 17.55 avrai:
17
0.55000000000000071
55.00000000000007105
55
17.55 "funziona" perchè la rappresentazione in virgola mobile è 55.000... non 54.999..., quindi quando lo tronchi a intero, ottieni 55.

ps: se stai usando lo standard C++11 o uno più recente, puoi importare cmath e usare la funzione round per arrotondare correttamente un valore in virgola mobile a intero. Altrimenti c'è sempre il trucchetto di aggiungere 0,5 al valore prima di troncarlo.
 
Ultima modifica:
ho un altro problema simile, ma con i double e int. L'ultimo risultato mi dà 53 e non 54.
C++:
#include <iostream>

using namespace std;

int main()
{
   double a = 17.54;  //Se metto 17.55 funziona
   int b = a;
   cout << b << endl;  //17
   double c = a - b;
   cout << c << endl;  //0.54
   c *= 100;
   cout << c << endl;  //54
   int d = c;  //Se metto float funziona
   cout << d;  //53. perché 53 e non 54?
}

Devi imparare a castare. Double non è un Int. Non puoi assegnare ad un int un double e viceversa.
C++:
double dpfp = 102.1;
int interger = static_cast<int>(dpfp);

Inoltre 100 non è un double, va scritto come 100.0.
 
Pubblicità
Pubblicità
Indietro
Top