9

Ho questa semplice riga di codice:Perché la variabile flottante salva il valore tagliando le cifre dopo il punto in un modo strano?

float val = 123456.123456; 

quando stampo questo val o guardo allo scopo, esso memorizza il valore 123456,13

Ok, va bene, non è possibile memorizzare tutte quelle cifre dopo punto appena in 4 byte, ma perché fa 13 dopo il punto? Non dovrebbe essere 12?

(usando VC++ 2010 Express su win32)

+0

grazie a tutti per le risposte. ho capito ora solo che dovrebbe cadere cifre troppo lunghe dopo il punto, invece di arrotondarlo. – Kosmos

risposta

7

Quando rappresentato come float, il numero ha un esponente di 16 (vale a dire il valore è il suo mantisse volte 2^16 o 65536). La mantissa diventa quindi

123456.123456/65536 = 1.8837909462890625 

Per adattarsi in un galleggiante 32 bit, la mantissa viene troncato a 23 bit, per cui ora diventa 1.883791. Quando viene moltiplicato per 65536, diventa 123456.125.

Nota il 5 nella terza posizione dopo il punto decimale: la routine di output del C++ che è stato utilizzato lo arrotonda, rendendo il numero finale simile a 123456.13.

EDIT Spiegazione del raccordo: (commento di Rick Regan)

L'arrotondamento avviene prima in formato binario (a 24 bit), in decimale alla conversione binaria, e quindi in decimale, in printf. Il valore memorizzato è 1.1110001001000000001 x 2^16 = 1.8837909698486328125 x 2^16 = 123456.125. Viene stampato come 123456.13, ma solo perché Visual C++ utilizza arrotondamenti "arrotondati a metà da zero".

Rick ha uno outstanding article on the subject, anche.

Se si desidera giocare con altri numeri e le loro rappresentazioni float, ecco un very useful IEEE-754 calculator.

+0

Potresti spiegare un po 'di più .. Non potrei capire in particolare questa riga "Quando rappresentato come un float, il tuo numero ha un esponente di 16" . Perché è necessario? –

+0

@RasmiRanjanNayak Il float a 32 bit è rappresentato da un mantisse a 23 bit, un esponente binario a sette bit e un bit di segno. Logicamente, si divide il numero originale per due e si aumenta l'esponente fino a quando l'unica cifra rimanente davanti al punto decimale è '1'. Per '123456.123456', sono necessarie 16 divisioni. – dasblinkenlight

+3

@dasblinkenlight Al meglio questa descrizione è fuorviante. L'arrotondamento si verifica dapprima in binario (a 24 bit), in conversione da decimale a binario e poi in decimale, in printf. Il valore memorizzato è 1.1110001001000000001 x 2^16 = 1.8837909698486328125 x 2^16 = 123456.125. Viene stampato come 123456.13, ma solo perché Visual C++ utilizza l'arrotondamento "arrotondato a metà da zero" (vedere il mio articolo http://www.exploringbinary.com/inconsistent-rounding-of-printed-floating-point-number /.) –

2

provare a stampare il valore di std::numeric_limits<float>::digits10. Questo è grosso modo quanto precisione ha in base 10 un galleggiante. Stai cercando di superarlo, quindi stai sperimentando una perdita di precisione (il che significa che le cifre oltre quelle significative non sono davvero significative).

Vedere ad es. What is the meaning of numeric_limits<double>::digits10

2

È totalmente dipendente dal compilatore. Controllalo in GCC. Dovrebbe essere xxx.12

+5

Se il formato utilizzato internamente è IEEE-754, non dovrebbe essere affatto dipendente dal compilatore. –

+0

Controllare http://steve.hollasch.net/cgindex/coding/ieeefloat.html –

+0

Penso che stiamo attribuendo due significati diversi a "compilatore-dipendente"; quello che io e gli altri abbiamo capito è che stai dicendo che "è normale che varia dal compilatore al compilatore". Stai dicendo invece che è un * bug specifico del compilatore *? –

8

Il valore memorizzato in val è uguale a 123456.125. Hai trovato .13 perché si sta arrotondando:

float val = 123456.123456; 
printf("%.4f %.2f\n", val, val); 

uscita: 123456.1250 123456.13

Si dovrebbe usare doppia in questo caso per evitare il troncamento. Il compilatore dovrebbe anche avvisarti: "avviso C4305: 'inizializzazione': troncamento da 'doppio' a 'mobile'".

10

In binario, 123456.123456 è 11110001001000000.000111111001 ... (infinito). Arrotonda a 11110001001000000.001 o 123456.125. Che arrotonda a 123456.13 quando stampato.

Problemi correlati