PROBLEMA C++: Float ingannevoli

Gabryy.

Utente Attivo
359
76
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
 
  • Mi piace
Reazioni: poltronaMAN

BAT

Moderatore
Staff Forum
Utente Èlite
22,883
11,540
CPU
1-Neurone
Dissipatore
Ventaglio
RAM
Scarsa
Net
Segnali di fumo
OS
Windows 10000 BUG
Non te lo speghi perché probabilmente non hai studiato la rappresentazione binaria dei numeri in virgola mobile;
un float a 32 bit ha al massimo 6-7 cifre significative (dipende dal numero), mentre un double a 64 bit ne ha 15.
Un sito per la verifica è il seguente:
https://www.h-schmidt.net/FloatConverter/IEEE754.html
 

Andretti60

Utente Èlite
6,440
5,091
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.
 

BAT

Moderatore
Staff Forum
Utente Èlite
22,883
11,540
CPU
1-Neurone
Dissipatore
Ventaglio
RAM
Scarsa
Net
Segnali di fumo
OS
Windows 10000 BUG
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
 

BAT

Moderatore
Staff Forum
Utente Èlite
22,883
11,540
CPU
1-Neurone
Dissipatore
Ventaglio
RAM
Scarsa
Net
Segnali di fumo
OS
Windows 10000 BUG
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
 
  • Mi piace
Reazioni: Andretti60

Andretti60

Utente Èlite
6,440
5,091
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 :)
 
  • Mi piace
Reazioni: Gabryy.

_Achille

Utente Èlite
3,067
725
CPU
Intel i5-6600K @4.6 GHz
Dissipatore
Cryorig H5
Scheda Madre
ASRock Z170 Extreme 6
HDD
WesternDigital 1TB & Crucial MX200 250GB
RAM
Corsair Ven 16GB DDR4 2133MHz
GPU
Sapphire RX 580 Nitro+
Monitor
Dell S2418H
PSU
RM550X
Case
NZXT S340
Periferiche
Anne Pro 2, Razer Abyssus
OS
Windows 10 Pro
if(a==0.7) // probabile errore
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?
 
  • Mi piace
Reazioni: poltronaMAN

Andretti60

Utente Èlite
6,440
5,091
.
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.
 

Gabryy.

Utente Attivo
359
76
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?
}
 
  • Mi piace
Reazioni: poltronaMAN

1nd33d

Utente Attivo
653
279
CPU
Intel i5 3570K @ 4,5Ghz
Dissipatore
Scythe Mugen 2
Scheda Madre
Gigabyte Z77X-UD3H
HDD
Samsung 840 PRO 256GB + Sandisk Ultra 250GB + Sandisk Plus 960GB
RAM
2x8GB Crucial Ballistix Tactical @2000Mhz CL9
GPU
XFX RX480 GTR Black Edition
Audio
Auzentech X-Fi Forte
Monitor
AOC i2369VW
PSU
Seasonic P660
Case
eh?
Periferiche
Razer Naga HEX v2
OS
Windows 10 64bit - Linux Mint 18
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:
  • Mi piace
Reazioni: BAT e Andretti60

_Achille

Utente Èlite
3,067
725
CPU
Intel i5-6600K @4.6 GHz
Dissipatore
Cryorig H5
Scheda Madre
ASRock Z170 Extreme 6
HDD
WesternDigital 1TB & Crucial MX200 250GB
RAM
Corsair Ven 16GB DDR4 2133MHz
GPU
Sapphire RX 580 Nitro+
Monitor
Dell S2418H
PSU
RM550X
Case
NZXT S340
Periferiche
Anne Pro 2, Razer Abyssus
OS
Windows 10 Pro
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.
 
  • Mi piace
Reazioni: BAT

Ci sono discussioni simili a riguardo, dai un'occhiata!

Entra

oppure Accedi utilizzando
Discord Ufficiale Entra ora!

Discussioni Simili