PDA

View Full Version : [C,C++] Problema con i float


Jonny32
04-04-2006, 18:27
Salve a tutti...
Ho uno strano problema riguardante la memorizzazione di un numero in una variabile float.
Uso gcc/g++ e sono sotto debian. Provate a compilare questo test:


// g++ test.c -O3 -o test
// gcc test.c -O3 -o test
#include <stdio.h>


int main ( ) {
float myfloat;
myfloat = 10.0 / 7.2123;

printf ( "mem -> \t%1.20f\n", myfloat );
printf ( "fresh -> \t%1.20f\n", 10.0 / 7.2123 );

return 0;
}


A me dà il seguente output:


./test
mem -> 1.38652026653289794922
fresh -> 1.38652025012825319550


Qualcuno mi sà aiutare?

Ziosilvio
04-04-2006, 20:07
Anzitutto, con la virgola mobile non puoi riprodurre esattamente tutte le frazioni, per cui entrano in gioco comunque degli errori.
Per la cronaca, due numeri in virgola mobile si considerano uguali non quando hanno la stessa rappresentazione, ma quando la loro differenza è inferiore in valore assoluto a una tolleranza opportuna.

Come seconda cosa, il primo output è un float, il secondo è un double.
Infatti, le costanti numeriche con il puntino sono per default double, e non float: quindi, 10.0 / 7.2123 è un'espressione numerica di tipo double, di cui perdi alcune cifre di precisione quando la converti in float per assegnarla a myfloat.

andbin
04-04-2006, 20:31
Salve a tutti...
Ho uno strano problema riguardante la memorizzazione di un numero in una variabile float.
Uso gcc/g++ e sono sotto debian. Provate a compilare questo test:


// g++ test.c -O3 -o test
// gcc test.c -O3 -o test
#include <stdio.h>


int main ( ) {
float myfloat;
myfloat = 10.0 / 7.2123;

printf ( "mem -> \t%1.20f\n", myfloat );
printf ( "fresh -> \t%1.20f\n", 10.0 / 7.2123 );

return 0;
}


A me dà il seguente output:


./test
mem -> 1.38652026653289794922
fresh -> 1.38652025012825319550


Qualcuno mi sà aiutare?
Eh ... non è che ci sia molto da fare ... si può dire che è normale!

Il problema è dovuto agli arrotondamenti nei calcoli floating point. Oltre agli arrotondamenti dovuti ai calcoli in sè, c'è pure da tenere presente gli errori/arrotondamenti causati dalla visualizzazione dei numeri con la printf, che per farti vedere il numero esegue a sua volta ... altri calcoli sui numeri.

Un float (32 bit) ha una parte significativa (mantissa) di 24 bit che equivale a una precisione di circa 7~8 digit.
Avresti un po' più di digit con il double (64 bit), che con una parte significativa di 53 bit ha una precisione di circa 15~16 digit.

Avresti lo stesso problema con questo codice:
float a = 34.18f;
float b = 34.17f;
float c;
float d = 0.01f;

c = a-b;

printf ("%f\n", c);
printf ("%f\n", d);

if (c == d)
printf ("Uguali\n");
else
printf ("Diversi\n");Infatti stampa:
0.010002
0.010000
Diversi

È un problema generale, dovuto alla rappresentazione dei numeri in floating point e non dipende dal compilatore o dal sistema operativo usato.

sottovento
05-04-2006, 06:45
float a = 34.18f;
float b = 34.17f;
float c;
float d = 0.01f;

c = a-b;

printf ("%f\n", c);
printf ("%f\n", d);

if (c == d)
printf ("Uguali\n");
else
printf ("Diversi\n");Infatti stampa:
0.010002
0.010000
Diversi



L'esempio presentato e' ottimo, anzi probabilmente il migliore possibile, poiche' l'operazione di sottrazione di due numeri "simili" tende ad elidere la parte "esatta" del numero, il quale verra' poi normalizzato nuovamente nella forma mantissa-esponente, producendo grandi errori.

Nel caso tu abbia problemi numerici, e' opportuno verificare le operazioni da fare a priori, e cercare la forma piu' conveniente. Ovviamente questi errori sono ineliminabili, ma in molti casi li puoi tenere sotto controllo.
Per esempio, in molti casi, semplicemente applicando la distributiva o cambiando l'ordine degli addendi, si puo' ridurre il problema...

High Flying
Sottovento

Jonny32
05-04-2006, 16:24
Grazie a tutti dell'interessamento...
Nella storia delle approssimazioni c'ero già inciampato altre volte, quello che proprio non riuscivo a capire era come due operazioni apparentemente uguali dassero output diverso... Sembrava quasi che memorizzare o no il valore in memoria influisse in qualche modo... :p


Invece era soltanto per il fatto che stavo operando con dei double invece che con dei float... :O Infatti così modificato (ho aggiunto f) stampa lo stesso output per i due float...


// g++ test.c -O3 -o test
// gcc test.c -O3 -o test
#include <stdio.h>


int main ( ) {
float myfloat;
myfloat = 10.0f / 7.2123f;

printf ( "mem -> \t%1.15f\n", myfloat );
printf ( "fresh -> \t%1.15f\n", 10.0f / 7.2123f );
printf ( "exact -> \t%1.15lf\n", 10.0 / 7.2123 );

return 0;
}


E fino alla 7° cifra significativa si tratta dello stesso numero... Finalmente svelato l'arcano! :sofico:

Grazie a tutti!!! :D


P.S.: Cmq il problema posto da andbin mi sembra abbastanza grave, almeno per coloro che volessero effettuare calcoli di tipo scientifico... Ci sono modi per evitare questo problema?

andbin
05-04-2006, 16:46
Infatti così modificato (ho aggiunto f) stampa lo stesso output per i due float...


// g++ test.c -O3 -o test
// gcc test.c -O3 -o test
#include <stdio.h>


int main ( ) {
float myfloat;
myfloat = 10.0f / 7.2123f;

printf ( "mem -> \t%1.15f\n", myfloat );
printf ( "fresh -> \t%1.15f\n", 10.0f / 7.2123f );
printf ( "exact -> \t%1.15lf\n", 10.0 / 7.2123 );

return 0;
}

C'è una cosa però ... il problema non è assegnare un float ad una variabile in memoria!

myfloat = 10.0f / 7.2123f;
e
printf ( "fresh -> \t%1.15f\n", 10.0f / 7.2123f );

danno sempre lo stesso identico valore perché sono espressioni formate da costanti e quindi calcolate e risolte a tempo di compilazione dal compilatore nello stesso identico modo!!!