2012-09-17 12 views
7

Cioè, per esempio, hanno il seguente numero codificato in IEEE-754 precisione singola:Come convertire float in double (entrambi memorizzati nella rappresentazione IEEE-754) senza perdere precisione?

"0100 0001 1011 1110 1100 1100 1100 1100" (approximately 23.85 in decimal) 

Il numero binario di cui sopra è memorizzato nella stringa letterale.

La domanda è, come posso convertire questa stringa in una rappresentazione a doppia precisione IEEE-754 (un po 'come la seguente, ma il valore non è lo stesso), SENZA perdere la precisione?

"0100 0000 0011 0111 1101 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1010" 

che è lo stesso numero codificato in IEEE-754 doppia precisione.

Ho provato a utilizzare il seguente algoritmo per convertire prima la prima stringa in numero decimale, ma perde precisione.

Sto usando Qt C++ Framework su piattaforma Windows.

MODIFICA: Devo scusarmi forse non ho ottenuto la domanda chiaramente espressa. Quello che voglio dire è che non conosco il vero valore 23.85, ho solo ottenuto la prima stringa e voglio convertirla in una rappresentazione a doppia precisione senza perdita di precisione.

+1

@tenfour Penso che sia perché sta immagazzinando come stringa – Caesar

+5

La seconda stringa binaria è _not_ lo stesso numero come il primo, ma in 'double' precisione. Qual è il problema che cerchi di risolvere? –

+1

Daniel Fisher è corretto; quelli non sono in alcun modo lo stesso numero (il primo è 23.84999847412109375, il secondo è 23.85000000000000142108547152020037174224853515625). –

risposta

3

Bene: tenere il bit di segno, riscrivere l'esponente (meno vecchi pregiudizi, più nuova bias), e pad mantissa con gli zeri a destra ...

(come dice @ Marco, è necessario trattare alcuni casi speciali separatamente, vale a dire quando l'esponente parziale è zero o max.)

+0

Grazie! La domanda, infatti, è così semplice. Sono rimasto bloccato nella mia mente. – Richard

+0

Ti rendi conto che così facendo NON otterrai il risultato che hai richiesto nell'OP, giusto? –

+0

@KerrekSB: in generale, ** non ** restituisce il valore decimale originale! Le versioni tra il numero frazionario binario e i numeri decimali decimali si basano su una bella interazione tra la conversione da decimale a binario e viceversa. Se si esegue il pad con zeri, il doppio non si convertirà correttamente. –

1

Potrebbe essere più semplice convertire la stringa in un float effettivo, convertirla in una doppia e convertirla in una stringa.

+0

+1 per una soluzione valida. Grazie – Richard

2

Prima di tutto, +1 per identificare l'input in binario.

Secondo, quel numero non rappresenta 23,85, ma leggermente meno. Se si capovolge l'ultima cifra binaria da 0 a 1, il numero non rappresenterà ancora correttamente 23.85, ma leggermente di più. Queste differenze non possono essere catturate adeguatamente in un float, ma possono essere catturate approssimativamente in un doppio.

In terzo luogo, cosa si prova che si sta perdendo si chiama precisione, non precisione. La precisione del numero cresce sempre con la conversione da precisione singola a doppia precisione, mentre la precisione non può mai migliorare con una conversione (il numero impreciso rimane inaccurato, ma la precisione aggiuntiva lo rende più evidente).

Si consiglia di convertire in un float o arrotondamento o aggiungere un valore molto piccolo appena prima di visualizzare (o registrare) il numero, perché l'aspetto visivo è quello che si è perso aumentando la precisione.

Resistere alla tentazione di arrotondare a destra dopo il cast e utilizzare il valore arrotondato nel calcolo successivo - questo è particolarmente rischioso nei cicli. Mentre questo potrebbe sembrare che corregge il problema nel debugger, le inaccuratezze aggiuntive accumulate potrebbero distorcere il risultato finale ancora di più.

2

IEEE-754 (e il punto mobile in generale) non possono rappresentare decimali binari periodici con precisione completa.Nemmeno quando, in realtà, sono numeri razionali con numeratore e denominatore intero relativamente piccolo. Alcune lingue forniscono un tipo razionale che può farlo (sono le lingue che supportano anche interi di precisione illimitati).

Di conseguenza, quei due numeri che hai pubblicato NON sono lo stesso numero.

Essi, infatti, sono:

10111,11011001100110011000000000000000000000000000000000000000 ... 10111,11011001100110011001100110011001100110011001101000000000 ...

dove ... rappresentano una sequenza infinita di 0 s.

Stephen Canon in un commento sopra fornisce i valori decimali corrispondenti (non li ha controllati, ma non ho motivo di dubitare che li abbia ottenuti correttamente).

Pertanto, la conversione che si desidera eseguire non può essere eseguita in quanto il numero di precisione singola non ha le informazioni necessarie (non si ha modo di sapere se il numero è in effetti periodico o semplicemente sembra essere perché accade essere una ripetizione).

+0

Scusa, non capisco. Si dice che nella Programmazione C non ci sia perdita di precisione nella conversione di "float" in "double", sembra un paradosso qui ... – Richard

+0

Non c'è perdita di precisione. Se converti il ​​float in raddoppio ottieni lo stesso valore. Il problema è che NON vuoi ottenere lo stesso valore, vuoi ottenere un altro valore. Pensatelo in decimale, un decimale in virgola mobile con 6 cifre di mantissa potrebbe memorizzare 123.4545 (come 1.234545 * 10^2). Se si passa a 8 cifre si ottiene 123.4545 (come 1.23454500 * 10^2) senza perdita di precisione. Ma quello che ti piacerebbe avere è 123.454545 che è un altro numero. –

-1

I punti mobili binari non possono, in generale, rappresentare esattamente i valori della frazione decimale. La conversione da un valore decimale frazionario a un punto mobile binario (vedere "Bellerophon" in "Come leggere i numeri in virgola mobile in modo accurato" da William D.Clinger) e da un valore in virgola mobile a un valore decimale (vedere "Dragon4" in "Come stampare accuratamente i numeri in virgola mobile" di Guy L.Steele Jr. e Jon L.White) producono i risultati attesi perché uno converte un numero decimale nel punto mobile binario rappresentabile più vicino e l'altro controlla l'errore per sapere quale valore decimale da cui proviene (entrambi gli algoritmi sono migliorati e resi più pratici in di David Gay. Gli algoritmi sono la base per il ripristino delle cifre decimali std::numeric_limits<T>::digits10 (eccetto, potenzialmente, gli zeri finali) da un valore in virgola mobile memorizzato nel tipo T.

Sfortunatamente, espandendo un float a un valore di double rovina il valore: tentando di formattare il nuovo numero in molti casi non si ottiene l'originale decimale poiché lo float riempito con zeri è diverso dal più vicino double Bellerophon creerebbe e, quindi, Dragon4 si aspetta. Ci sono fondamentalmente due approcci che funzionano abbastanza bene, però:

  1. Come qualcuno ha suggerito di convertire il float in una stringa e questa stringa in un double. Questo non è particolarmente efficiente, ma può essere dimostrato che produce i risultati corretti (presupponendo una corretta implementazione degli algoritmi non del tutto banali, ovviamente).
  2. Supponendo che il valore sia compreso in un intervallo ragionevole, è possibile moltiplicarlo per una potenza di 10 tale che la cifra decimale meno significativa non sia zero, convertire questo numero in numero intero, questo intero in uno double e infine dividere risultante doppio dalla potenza originale di 10. Non ho una prova che questo produce il numero corretto ma per l'intervallo di valori a cui sono interessato e che voglio memorizzare con precisione in un float, questo funziona.

Un approccio ragionevole per evitare questo problema è interamente usare decimale virgola mobile valori come descritto per C++ nella Decimal TR in primo luogo. Sfortunatamente, questi non sono ancora parte dello standard, ma ho presentato una proposta al comitato di standardizzazione del C++ per ottenere questo cambiamento.

+0

Il problema è quello di convertire un numero binario a virgola mobile a un numero binario a virgola mobile, ma questa risposta offre affermazioni irrilevanti circa rappresentano valori decimali e la conversione da e decimale. Inoltre, la sua affermazione che l'espansione di un float in una doppia devastazione del valore non ha senso: il valore non cambia. (Se qualche pezzo di software per convertire il doppio in decimale quindi visualizza in modo diverso dal galleggiante, allora questo è il difetto di tale software e non ha nulla a che fare con il problema originale.) –

+0

Un errore comune che interferisce con gli sforzi di ragionare sulle i numeri in virgola mobile sono convinti che un 'float' rappresenta una quantità esatta della forma M * 2^N, dove M e N sono numeri interi in un certo intervallo. È vero che un 'float' ha un valore" nominale "esatto di quella forma, ma non direi che un' float' rappresenta quella quantità esatta. Il numero '2000000.13f' ha un valore nominale di esattamente 2000000.125, ma quel particolare' float' verrebbe utilizzato per tutti i valori da 2000000.0625 a 2000000.1875. Poiché tutti i valori da ... – supercat

+0

2000000,1200 a 2000000,1299 sarebbero all'interno della gamma possibile, cifre oltre il secondo dopo la virgola trasmettere alcuna informazione utile; il valore riportato viene arrotondato a 2000000.13. Sebbene non ci sia 'double' il cui valore nominale esatto sia 2000000.125 (corrispondente al float), non c'è nessuno che significhi" qualcosa tra 2000000.0625 e 2000000.1875 ". Il 'double' con lo stesso valore nominale ha un significato semantico diverso. – supercat