2011-01-10 9 views
18

Ho bisogno di utilizzare un file di testo esistente per memorizzare alcuni valori molto precisi. Quando vengono letti di nuovo, i numeri devono essenzialmente essere esattamente equivalenti a quelli che sono stati originariamente scritti. Ora, una persona normale userebbe un file binario ... per una serie di ragioni, non è possibile in questo caso.il modo migliore per generare una doppia precisione completa in un file di testo

Quindi ... qualcuno di voi ha un buon modo di codificare un doppio come una stringa di caratteri (oltre ad aumentare la precisione). Il mio primo pensiero è stato quello di assegnare il doppio a un carattere [] e scrivere i caratteri. Non penso che funzionerà perché alcuni dei personaggi non sono visibili, producono suoni e terminano anche le stringhe ('\ 0' ... Ti sto parlando!)

Pensieri?

[Modifica] - una volta individuata quale delle soluzioni proposte funziona meglio per me, ne contrassegnerò una come "la" soluzione.

+1

Se si vuole essere portatile si può assumere nulla circa la rappresentazione del numero in virgola mobile (non v'è alcuna definizione nella standard del rappresentato). Quindi il ** SOLO ** modo di fare questo portatile è stampare semplicemente il numero con la massima precisione possibile. Ora se vuoi buttare via la portabilità allora puoi avere un formato binario (codificato in Base64 se vuoi). Ma poi perderai precisione quando convertirai nel formato float specifico della piattaforma (a meno che non sia esattamente lo stesso del sistema sorgente). Ma poi non hai guadagnato nulla sulla stampa con precisione assoluta. –

+0

Scrivi i caratteri come testo, cioè converti 'c/16' e' c% 16' in un carattere (0-9 o A-F) e stampali. – user168715

+2

Una cosa da considerare è che il compilatore può assegnare i doppi ai registri della CPU con più di 64 bit di precisione. Quando questi valori vengono scritti in memoria in preparazione della scrittura su disco, verranno troncati a 64 bit. Quindi, anche se si salva il doppio in binario e lo si legge, il valore letto non è garantito == l'originale. – user168715

risposta

0

Prova questa:

double d = 0.2512958125912; 
std::ostringstream s; 
s << d; 

quindi scrivere s su file.

+0

Ho provato questo. Con valori molto precisi fallisce – fbl

2

È possibile utilizzare la base 64. Ciò consentirebbe di memorizzare i valori di byte esatti in un file di testo.

Non l'ho usato, ma ho trovato questa codifica/decodifica base 64 library per C++.

+3

Tranne che ha ** niente ** fare con virgola mobile: solo perché le persone lo usano per codificare i dati binari non significa che puoi codificare i numeri in virgola mobile e aspettarti che escano correttamente dall'altra parte !!!! –

+2

poiché la maggior parte dei sistemi tende a seguire IEEE 754, è possibile codificare il punto mobile come dati binari piuttosto bene – fuzzyTew

+0

@fuzzyTew: c'è sempre una scelta: o sacrificare la portabilità, o precisione :) – ruslik

0

Non si dice perché il binario è off limits. Per la tua applicazione sarebbe possibile convertire il file binario in una stringa ASCII esadecimale?

+0

La mia unica limitazione è che devo esportare in un file di testo chiaro. Ci sono altre colonne nel file a cui gli utenti devono accedere (usando Excel, Matlab, ecc.). Voglio avere questi dati nello stesso file e scrivere altri strumenti che possono resuscitare il valore equivalente binario. – fbl

1

ero sicuro c'era un identificatore speciale formato per printf (forse %a?) Che ha permesso di stampare la rappresentazione binaria di un galleggiante, ma non riesco a trovarlo ..
Tuttavia, si può provare questo:

int main(int argc, char* argv[]){ 
    union fi { 
     unsigned int i; 
     float  f; 
    } num; 
    num.f = 1.23f; 
    printf("%X\n", num.i); 
    return 0; 
} 
+1

Non aiuta. Né le rappresentazioni dei numeri interi né quelle in virgola mobile sono garantite, pertanto la conversione in un numero intero consente di stampare un numero ma non garantisce che un altro sistema generi il valore in virgola mobile. (Inoltre dovresti aggiungere i controlli del tempo di compilazione con float/int della stessa dimensione). –

+3

Si potrebbe fare ciò che dice ruslik e definire l'output come IEEE 754. Su qualsiasi piattaforma in cui questo non è il caso, si dovrà fare la conversione del software del doppio. – KitsuneYMG

+1

ovviamente, questo codice è in c invece di C++ (tag domanda) e funziona su float invece di double - ma risolve il problema – fuzzyTew

0

Rappresentazione di archiviazione a parte, che dire di qualcosa di simile. Tuttavia, valori speciali come -0, infinito, NaN ecc richiedono una gestione speciale . Inoltre ho "dimenticato" di implementare esponenti negativi.

#include <stdio.h> 
#include <math.h> 

const int SCALE = 1<<(52/2); 

void put(double a) { 
    FILE* f = fopen("dump.txt", "wb"); 
    int sign = (a<0); if(sign) a=-a; 
    int exp2 = 0; while(a>1) a/=2, exp2++; 
    a*=SCALE; 
    int m1 = floor(a); 
    a = (a-m1)*SCALE; 
    int m2 = floor(a); 
    fprintf(f, "%i %i %i %i\n", sign, exp2, m1, m2); 
    fclose(f); 
} 

double get(void) { 
    FILE* f = fopen("dump.txt", "rb"); 
    double a; 
    int sign, exp2, m1, m2; 
    fscanf(f, "%i %i %i %i\n", &sign, &exp2, &m1, &m2); 
    fclose(f); 
    printf("%i %i %i %i\n", sign, exp2, m1, m2); 
    a = m2; a /= SCALE; 
    a+= m1; a /= SCALE; 
    while(exp2>0) a*=2, exp2--; 
    if(a<0) a=-a; 
    return a; 
} 

int main(void) { 
    union { 
    double a; 
    unsigned b[2]; 
    }; 
    a = 3.1415926; 
    printf("%.20lf %08X %08X\n", a, b[0], b[1]); 
    put(a); 
    a = get(); 
    printf("%.20lf %08X %08X\n", a, b[0], b[1]); 
} 
4

Un processo in due fasi: in primo luogo utilizzare binary float/double serialization e quindi applicare base 64 encoding. Il risultato non è leggibile dall'uomo, ma non perderà la precisione.

Edit: (Grazie a fuzzyTew e dan04)

Lossless rappresentazione decimale ed leggibile è probabilmente possibile, ma richiederebbe molto più spazio.

+2

È certamente possibile creare una rappresentazione leggibile in grado di rappresentare esattamente un punto in virgola mobile numero. – fuzzyTew

+1

Destra: 2 è un fattore di 10, quindi tutte le frazioni binarie di terminazione terminano anche nella base 10. Anche se potrebbero essere necessarie molte cifre, ad esempio 0.1000000000000000055511151231257827021181583404541015625. – dan04

+1

Ma non è possibile rappresentare il virgola mobile decimale come un punto mobile binario. Ho ragione? –

9

Supponendo che IEEE 754 doppio, printf("%.17g\n", x) fornirà cifre sufficienti per ricreare il valore originale.

+0

qual è il modo migliore per analizzarlo? quali sono le opzioni per preservare gli infiniti e forse NaN? – fuzzyTew

+0

http://www.cplusplus.com/forum/beginner/30400/ – Shelwien

+0

È possibile rilevare tutti i casi speciali, come in http://www.cplusplus.com/forum/beginner/30400/, ma normalmente perché si dovrebbe ne hai bisogno? Inoltre, la rappresentazione decimale è sicuramente piacevole e leggibile, ma devi ricordare che sia la mantissa che l'esponente nei doppi IEEE sono binari, quindi sarebbe difficile conservare tutti i bit ... probabilmente avresti bisogno delle tue funzioni bin2dec, dec2bin Comunque. – Shelwien

13

Se si desidera mantenere il formato rigorosamente leggibile, è possibile scrivere il doppio così:

#include <iomanip> 
#include <sstream> 

std::string doubleToText(const double & d) 
{ 
    std::stringstream ss; 
    //ss << std::setprecision(std::numeric_limits<double>::digits10+2); 
    ss << std::setprecision(std::numeric_limits<int>::max()); 
    ss << d; 
    return ss.str(); 
} 

std::numeric_limits<int>::max() uscita volontà con la massima precisione possibile decimale. Ciò preserverà il valore più precisamente attraverso implementazioni in virgola mobile differenti.Lo scambio di quella linea per la riga commentata utilizzando std::numeric_limits<double>::digits10+2 darà la precisione sufficiente a rendere il doppio esattamente recuperabile sulla piattaforma per cui il codice è stato compilato. Ciò fornisce un output molto più breve e conserva tutte le informazioni che la doppia può rappresentare in modo univoco.

Il C++ operatori streaming non conservano numeri denormalizzati o gli infiniti e non-a-numeri durante la lettura stringhe. Tuttavia, la funzione POSIX strtodfa, ed è definito pari a dallo standard. Quindi, il modo più preciso per leggere un numero decimale indietro con una chiamata di libreria standard sarebbe questa funzione:

#include <stdlib.h> 

double textToDouble(const std::string & str) 
{ 
    return strtod(str.c_str(), NULL); 
} 
+0

La funzione 'strtod' sembra la soluzione più indolore e completa. – Eonil

+2

'std :: numeric_limits :: max_digits10' è uno standard più di' std :: numeric_limits :: digit10 + 2' – ead

1

Per stampare lunghi elenchi di numeri in C++ senza perdita (scrittura e lettura nella stessa arquitecture) Io uso questo (per double s):

#include<iostream> 
#include<iomanip> 
#include<limits> 
#include<cmath> 

#include<sstream> 
int main(){ 
std::ostringstream oss; 

int prec = std::numeric_limits<double>::digits10+2; // generally 17 

int exponent_digits = std::log10(std::numeric_limits<double>::max_exponent10)+1; // generally 3 
int exponent_sign = 1; // 1.e-123 
int exponent_symbol = 1; // 'e' 'E' 
int digits_sign = 1; 
int digits_dot = 1; // 1.2 

int division_extra_space = 1; 
int width = prec + exponent_digits + digits_sign + exponent_sign + digits_dot + exponent_symbol + division_extra_space; 

double original = -0.000013213213e-100/33215.; 
oss << std::setprecision(prec) << std::setw(width) << original << std::setw(width) << original << std::setw(width) << original << '\n'; 
oss << std::setprecision(prec) << std::setw(width) << 1. << std::setw(width) << 2. << std::setw(width) << -3. << '\n'; 
} 

stampe

-3.9780861056751466e-110 -3.9780861056751466e-110 -3.9780861056751466e-110 
         1      2      -3 

in sintesi, nel mio caso è come impostazione:

oss << std::precision(17) << std::setw(25) << original << ...; 

In ogni caso posso verificare se questo funziona, facendo:

std::istringstream iss(oss.str()); 
    double test; iss >> test; 
    assert(test == original); 
Problemi correlati