2011-01-10 9 views
18

Necesito usar un archivo de texto existente para almacenar algunos valores muy precisos. Cuando se vuelva a leer, los números esencialmente deben ser exactamente equivalentes a los que se escribieron originalmente. Ahora, una persona normal usaría un archivo binario ... por una serie de razones, eso no es posible en este caso.mejor manera de generar una doble precisión completa en un archivo de texto

Entonces ... ¿alguno de ustedes tiene una buena manera de codificar un doble como una cadena de caracteres (aparte de aumentar la precisión). Mi primer pensamiento fue lanzar el doble a un char [] y escribir los caracteres. No creo que eso vaya a funcionar porque algunos de los caracteres no son visibles, producen sonidos e incluso terminan cadenas ('\ 0' ... ¡estoy hablando de ti!)

¿Pensamientos?

[Editar] - una vez que descubro cuál de las soluciones propuestas funciona mejor para mí, marcaré una como "la" solución.

+1

Si quieres ser portátil que puede asumir nada acerca de la representación del número en coma flotante (no existe una definición en el estándar de los representados). Por lo tanto, la forma ** ÚNICA ** de hacer este portátil es simplemente imprimir el número con tanta precisión como sea posible. Ahora, si desea descartar la portabilidad, puede tener un formato binario (codificado en Base64 si lo desea). Pero luego perderá precisión al convertir al formato de flotación específico de la plataforma (a menos que sea exactamente igual que el sistema fuente). Pero luego no ha ganado nada con la impresión con total precisión. –

+0

Escriba los caracteres como texto, es decir, convierta 'c/16' y' c% 16' a un carácter (ya sea 0-9 o A-F) e imprímalos. – user168715

+2

Una cosa a considerar es que su compilador puede asignar dobles a registros de CPU con más de 64 bits de precisión. Cuando estos valores se escriben en la memoria como preparación para escribirlos en el disco, se truncan a 64 bits. Por lo tanto, incluso si guarda el doble en binario y lo lee, el valor de lectura no se garantiza a == el original. – user168715

Respuesta

0

Prueba esto:

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

Luego escribe s en un archivo.

+0

He intentado esto. Con valores muy precisos falla. – fbl

2

Puede usar la base 64. Esto le permitiría almacenar los valores exactos de bytes en un archivo de texto.

No lo he usado, pero encontré esta base 64 de codificación/decodificación library para C++.

+3

Excepto que tiene ** nada ** para hacer con punto flotante. ¡Solo porque la gente lo use para codificar datos binarios no significa que puedas codificar números en coma flotante y esperar que salgan por el otro extremo correctamente! –

+2

ya que la mayoría de los sistemas tienden a seguir a IEEE 754, puede codificar el punto flotante como datos binarios bastante bien – fuzzyTew

+0

@fuzzyTew: siempre hay una elección: sacrifica la portabilidad, ya sea precisión :) – ruslik

0

No dice por qué el binario está fuera de los límites. Para su aplicación, ¿sería factible contornear el binario a una cadena hexadecimal ASCII?

+0

Mi única restricción es que debo enviar a un archivo de texto claro. Hay otras columnas en el archivo a las que los usuarios deben acceder (usando Excel, Matlab, etc.). Quiero tener estos datos en el mismo archivo y escribir otras herramientas que puedan resucitar el valor equivalente binario. – fbl

1

yo estaba seguro de que era un especificador de formato especial para printf (tal vez %a?) Que permitía la impresión de la representación binaria de un flotador, pero yo no lo encuentro ..
Sin embargo, puede intentar esto:

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

No ayuda. Ni las representaciones de números enteros ni de coma flotante están garantizadas, por lo que la conversión a un número entero le permite imprimir un número, pero no garantiza que otro sistema genere el valor de coma flotante. (También debe agregar comprobaciones de tiempo de compilación que flotan/int tienen el mismo tamaño). –

+3

Puedes hacer lo que dice ruslik y definir el resultado como IEEE 754. En cualquier plataforma donde no sea el caso, tendrás que hacer una conversión de software del doble. – KitsuneYMG

+1

por supuesto, este código está en c en lugar de C++ (tag de pregunta) y funciona en flotantes en lugar de en dobles, pero resuelve el problema – fuzzyTew

0

Representación de almacenamiento a un lado, ¿qué tal algo así. Sin embargo, los valores especiales -0, infinitos, NaN, etc. requerirán un manejo especial de . También "olvidé" implementar los exponentes negativos.

#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 proceso de dos pasos: En primer lugar utilizar binary float/double serialization y luego aplicar base 64 encoding. El resultado no es legible por humanos, pero no perderá precisión.

Editar: (Gracias a fuzzyTew y dan04)

Lossless decimal y representación legible por humanos es probable que sea posible, pero requeriría mucho más espacio.

+2

Es ciertamente posible crear una representación humana legible capaz de representar exactamente un punto flotante binario número. – fuzzyTew

+1

Derecha: 2 es un factor de 10, por lo que todas las fracciones binarias terminadas también terminan en la base 10. Aunque podría tomar muchos dígitos, como en 0.1000000000000000055511151231257827021181583404541015625. – dan04

+1

Pero no es posible representar el punto flotante decimal como un punto flotante binario. ¿Estoy en lo cierto? –

9

Suponiendo que IEEE 754 double, printf("%.17g\n", x) le dará suficientes dígitos para volver a crear el valor original.

+0

¿cuál es la mejor manera de analizar eso? ¿Cuáles son las opciones para preservar infinitos y quizás NaN? – fuzzyTew

+0

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

+0

Puede detectar todos los casos especiales, como en http://www.cplusplus.com/forum/beginner/30400/, pero normalmente por qué ¿LE ¿necesita que? Además, la representación decimal es agradable y legible, pero debes recordar que tanto mantissa como exponente en IEEE son binarios, por lo que sería difícil conservar todos los bits ... probablemente necesitarías tus propias funciones bin2dec, dec2bin de todas formas. – Shelwien

13

Si desea mantener el formato legible estrictamente humano, puede escribir el doble de esta manera:

#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() dará salida a la máxima precisión decimal posible. Esto preservará el valor de forma más precisa en implementaciones de coma flotante diferente.Al intercambiar esa línea por la línea comentada usando std::numeric_limits<double>::digits10+2 se obtendrá la precisión suficiente para hacer que el doble sea precisamente recuperable en la plataforma para la que se compila el código. Esto proporciona un resultado mucho más corto y conserva toda la información que el doble puede representar de manera única.

El C++ operadores de flujo no conservan números normalizados o los infinitos y no-a-números cuando la lectura de cadenas. Sin embargo, la función POSIX strtodhace, y se define por el estándar. Por lo tanto, la forma más precisa para leer un número decimal de vuelta con una llamada a la librería estándar sería esta función:

#include <stdlib.h> 

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

La función 'strtod' parece la solución más indolora y completa. – Eonil

+2

'std :: numeric_limits :: max_digits10' es un estándar más que' std :: numeric_limits :: digits10 + 2' – ead

1

Para imprimir largas listas de números en C++ sin pérdida (de escritura y lectura en la misma arquitectura) que utilizo esto (por 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'; 
} 

grabados

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

en resumen, en mi caso es como el establecimiento:

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

En cualquier caso puedo probar si esto funciona, haciendo:

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