2010-09-24 13 views
5

Si consideri il seguente frammento di codice:In che modo printf e scanf gestiscono i formati di precisione in virgola mobile?

float val1 = 214.20; 
double val2 = 214.20; 

printf("float : %f, %4.6f, %4.2f \n", val1, val1, val1); 
printf("double: %f, %4.6f, %4.2f \n", val2, val2, val2); 

quali uscite:

float : 214.199997, 214.199997, 214.20 | <- the correct value I wanted 
double: 214.200000, 214.200000, 214.20 | 

Capisco che 214.20 ha una rappresentazione binaria infinita. I primi due elementi della prima linea hanno un'approssimazione del valore previsto, ma il l'ultimo sembra non avere approssimazione a tutti, e questo mi ha portato alla seguente domanda:

Come fare il scanf, fscanf, Le funzioni printf, fprintf (ecc.) Trattano i formati di precisione?

In assenza di precisione, printf ha stampato un valore approssimativo, ma con %4.2f ha fornito il risultato corretto. Puoi spiegarmi l'algoritmo usato da queste funzioni per gestire la precisione?

risposta

10

Il fatto è che 214.20 non può essere espresso esattamente con rappresentazione binaria. Pochi numeri decimali possono. Quindi viene memorizzata un'approssimazione. Ora, quando si utilizza printf, la rappresentazione binaria viene trasformata in una rappresentazione decimale, ma non può essere espressa esattamente e viene solo approssimata.

Come hai notato, puoi dare una precisione a printf per dire come arrotondare l'approssimazione decimale. E se non gli dai una precisione, si assume una precisione di 6 (vedi la pagina man per i dettagli).

Se si utilizza %.40f per il galleggiante e %.40lf per il doppio nel tuo esempio sopra, si ottiene questi risultati:

214.1999969482421875000000000000000000000000 
214.1999999999999886313162278383970260620117 

Sono diversi perché con il doppio, non ci sono più bit per una migliore approssimativa 214.20. Ma come puoi vedere, sono ancora molto strani se rappresentati in decimali.

Si consiglia di leggere il numero Wikipedia article on floating point numbers per ulteriori informazioni su come funzionano i numeri in virgola mobile. Una lettura eccellente è anche What Every Computer Scientist Should Know About Floating-Point Arithmetic

+0

Grazie per aver risposto, anche se la mia domanda è ancora nello stesso posto. In primo luogo, il float NON può contenere il valore esatto di 214.20, quindi come potrebbe recuperarlo SOLO con precisione di% 4.2f e NON con% 4.6f. Cosa ha fatto per riconquistare il valore. Capisco che l'approssimazione è stata fatta, ma COSA E 'STATO USATO IL MODELLO DEL PUNTO CHE HA CAUSATO MEMORIZZATO INTERNAMENTE 214.199997 PER DIVENTARE IL 214.20 E COME CI SIAMO ARRIVATI? –

+0

Non sono sicuro di averti, ma questo è solo il [arrotondamento] (http://en.wikipedia.org/wiki/Rounding). È un puro caso che '% 4.2f' ti dia esattamente il valore che volevi. È ancora "sbagliato" poiché la rappresentazione binaria è * non * equivalente a 214,20 ma a 214,1999969482421875. Ora, se giri "214.1999969482421875" a 6 cifre, ottieni "214.199997" perché la settima cifra è una 9, trasformando la 6 in una 7. Ma quando giri solo fino a 2 cifre, ottieni "214.20" perché la terza cifra è un nove, quindi è necessario aumentare il 9 sulla seconda cifra. Che "trabocca" a 10, trasformando ".19" in ".20". – DarkDust

+0

Oh !! Credo che tu stia cercando di dire che questa operazione è fatta in una rappresentazione decimale, ed è così che il 214.199997 si è formato come un effetto rotondo di doppia precisione. Perché se fosse stato in rappresentazione binaria abbiamo già confermato che 214.20 NON è rappresentabile in binario, con tutte le abilità di arrotondamento utilizzate. –

1

scanf arrotonderà il valore di input al valore in virgola mobile esattamente più prossimo. Come illustra la risposta di DarkDust, in precisione singola il valore più vicino esattamente rappresentabile è inferiore al valore esatto, e in doppia precisione il valore più vicino esattamente rappresentabile è superiore al valore esatto.

+0

Se fosse possibile farlo, perché abbiamo archiviato l'approssimazione al primo posto? :-). Scusami se sto causando dolore, ma che cosa volevo sapere, che cosa fa scanf per rappresentare un valore che è stato dato in sei punti dopo il decimale per essere convertito in due punti dopo i decimali. Dato che .20 NON è rappresentabile in virgola mobile binario –

+0

Sembra che questa domanda di follow-up venga risolta nella finestra di dialogo dei commenti con DarkDust. –

4

Dal momento che hai chiesto di scanf, una cosa che dovreste notare è che POSIX richiede printf e una successiva scanf (o strtod) per ricostruire il valore originale cifre esattamente purché sufficientemente significativi (almeno DECIMAL_DIG, credo) sono stati stampati Naturalmente C non richiede tale requisito; lo standard C praticamente consente alle operazioni in virgola mobile di fornire qualunque risultato piaccia al realizzatore fino a quando lo documentano. Tuttavia, se il tuo intento è quello di memorizzare numeri in virgola mobile in file di testo da rileggere in un secondo momento, è meglio usare l'identificatore C99 %a per stamparli in esadecimale. In questo modo saranno esatti e non c'è confusione sul fatto che il processo di serializzazione/deserializzazione perda precisione.

Tieni presente che quando ho detto "ricostruisci il valore originale", intendo il valore effettivo contenuto nella variabile/espressione passato a printf, non il decimale originale che hai scritto nel file sorgente che è stato arrotondato al migliore rappresentazione binaria dal compilatore.

Problemi correlati