2009-08-21 15 views
12

Estoy buscando una función de biblioteca para convertir números de coma flotante a cadenas, y viceversa, en C++. Las propiedades que quiero son ese str2num (num2str (x)) == x y ese num2str (str2num (x)) == x (lo más lejos posible). La propiedad general es que num2str debe representar el número racional más simple que al redondear al número de puntero flotante representable más cercano devuelve el número original.Precise punto flotante <-> conversión de cadena

Hasta ahora he intentado impulso :: lexical_cast:

double d = 1.34; 
string_t s = boost::lexical_cast<string_t>(d); 
printf("%s\n", s.c_str()); 
// outputs 1.3400000000000001 

Y he tratado std :: ostringstream, que parece funcionar para la mayoría de los valores, si lo hago stream.precision (16). Sin embargo, con una precisión de 15 o 17, trunca o da resultados desagradables para cosas como 1.34. No creo que se garantice que la precisión 16 tenga propiedades particulares que requiera, y sospecho que se descompone en muchos números.

¿Hay una biblioteca C++ que tenga tal conversión? O tal función de conversión ya está enterrada en algún lugar de las bibliotecas/impulso estándar.

La razón para querer estas funciones es guardar valores de coma flotante en archivos CSV, y luego leerlos correctamente. Además, me gustaría que los archivos CSV contengan números simples en la medida de lo posible para que puedan ser consumidos por humanos.

Sé que las funciones de lectura/demostración de Haskell ya tienen las propiedades que estoy buscando, al igual que las bibliotecas de BSD C. Las referencias estándar para cadena < -> doble conversiones es un par de papeles de PLDI 1990:

  • Cómo leer los números de punto con precisión flotante, Will Klinger
  • Cómo imprimir números de coma flotante con precisión, Guy Steele et al

Cualquier biblioteca C++/función basada en ellos sería adecuada.

EDITAR: Estoy totalmente consciente de que los números de punto flotante son representaciones inexactas de números decimales, y que 1.34 == 1.3400000000000001. Sin embargo, como los documentos de referencia sobre el punto de salida, eso no es excusa para elegir a aparecer como "1.3400000000000001"

Edit2: Este documento explica exactamente lo que estoy buscando: http://drj11.wordpress.com/2007/07/03/python-poor-printing-of-floating-point/

+0

Cuando busqué uno, encontré uno en C, no en C++. No tengo el enlace aquí. Me parece recordar que estaba en el sitio ftp de NAG, pero podría estar equivocado. – AProgrammer

+0

Una biblioteca de C es igualmente buena: ahora solo estoy revisando los documentos de NAG. –

+0

Puede buscar GMP y MPFR para la emulación de coma flotante de software. Pero lo que estás pidiendo es casi imposible con los tipos 'float' y' double' de C++. – greyfade

Respuesta

3

creo que esto hace lo que quiere, en combinación con strtod de la biblioteca estándar():

#include <stdio.h> 
#include <stdlib.h> 

int dtostr(char* buf, size_t size, double n) 
{ 
    int prec = 15; 
    while(1) 
    { 
    int ret = snprintf(buf, size, "%.*g", prec, n); 
    if(prec++ == 18 || n == strtod(buf, 0)) return ret; 
    } 
} 

Una demostración sencilla, que no se molesta en comprobar las palabras de entrada para la basura de salida:

int main(int argc, char** argv) 
{ 
    int i; 
    for(i = 1; i < argc; i++) 
    { 
    char buf[32]; 
    dtostr(buf, sizeof(buf), strtod(argv[i], 0)); 
    printf("%s\n", buf); 
    } 
    return 0; 
} 

algunas entradas de ejemplo:

% ./a.out 0.1 1234567890.1234567890 17 1e99 1.34 0.000001 0 -0 +INF NaN 
0.1 
1234567890.1234567 
17 
1e+99 
1.34 
1e-06 
0 
-0 
inf 
nan 

me imagine a su biblioteca de C tiene que cumplir con algunos versión suficientemente reciente del estándar para garantizar el redondeo correcto.

No estoy seguro de haber elegido los límites ideales en prec, pero imagino que deben estar cerca. Tal vez podrían ser más estrictos? Del mismo modo, creo que 32 caracteres para buf siempre son suficientes, pero nunca necesarios. Obviamente, todo esto supone dobles IEEE de 64 bits. Podría valer la pena verificar esa suposición con algún tipo de directiva inteligente de preprocesador: sizeof(double) == 8 sería un buen comienzo.

El exponente es un poco desordenado, pero no sería difícil de arreglar después de salir del circuito pero antes de volver, tal vez usando memmove() o similar para cambiar las cosas hacia la izquierda. Estoy bastante seguro de que habrá como máximo un + y como máximo un 0 líder, y no creo que ambos puedan ocurrir al mismo tiempo para prec >= 10 más o menos.

Del mismo modo, si prefieres ignorar iniciado sesión cero, como lo hace el Javascript, que puede manejar fácilmente por adelantado, por ejemplo:

if(n == 0) return snprintf(buf, size, "0"); 

tengo curiosidad para ver una comparación detallada con la línea 3000 monstruosidad que desenterraste en la base de código de Python. Presumiblemente, la versión corta es más lenta, menos correcta o algo así. Sería decepcionante si no fuera ...

+1

Investigué en un banco de pruebas de referencia. Los resultados en VS2008 de su versión son igual de buenos, pero no idénticos, por ejemplo, el primer algoritmo prefiere 87.21565540666982, mientras que el suyo prefiere 87.21565540666983, pero ambos tienen la misma representación de bits. Tu algoritmo también es un 3% más lento. Pero dado 1000 de líneas de C feo frente a tu respuesta bastante elegante, definitivamente ganas :). –

0

En realidad, yo creo que encontrará que 1.34 es 1.3400000000000001. Los números de punto flotante no son precisos. No puedes soslayar esto. 1.34f es 1.34000000333786011 por ejemplo.

+2

Exactamente, ¿por qué no mostrar el más corto cuando se le da 1.34f? Eso es todo lo que estoy pidiendo :-) –

0

Según lo manifestado por otros. Los números de coma flotante no son tan precisos, es un artefacto sobre cómo almacenan el valor.

Lo que realmente está buscando es una representación numérica decimal. Básicamente esto usa un número entero para almacenar el número y tiene una precisión específica después del punto decimal.

Una búsqueda rápida en Google consiguió esto: http://www.codeproject.com/KB/mcpp/decimalclass.aspx

+0

En general, los números decimales son, por supuesto, preferidos, pero en este caso realmente quiero usar números de punto flotante debido a otras restricciones en el sistema. –

+0

En realidad, tiendo a preferir los números racionales: http://haskell.org/ghc/docs/latest/html/libraries/base/Data-Ratio.html - tienen un poder de representación mucho mayor. –

+0

Sí. Lo he visto como una forma de representar la aritmética de precisión arbitraria (pero eso no es lo que preguntaste). Desea (ed) un método para representar un valor de coma flotante de precisión arbitraria (similar pero no igual). Entonces, ¿CUÁLES son las restricciones no mencionadas que te obligan a usar un valor de punto de flujo? –

1

La razón para querer estas funciones es guardar valores de punto flotante en archivos CSV, y luego leerlos correctamente. Además, me gustaría que los archivos CSV contengan números simples en la medida de lo posible para que puedan ser consumidos por humanos.

No puede haber conversión double → string → double y al mismo tiempo tener la cadena legible por humanos.

Necesita elegir entre una conversión exacta y una cadena legible por humanos.Esta es la definición de max_digits10 y digits10:

Aquí es una implementación de num2str y str2num con dos contextos diferentes from_double (doble conversión → cadena → doble) y from_string (cadena de conversión → doble → cadena):

#include <iostream> 
#include <limits> 
#include <iomanip> 
#include <sstream> 

namespace from_double 
{ 
    std::string num2str(double d) 
    { 
    std::stringstream ss; 
    ss << std::setprecision(std::numeric_limits<double>::max_digits10) << d; 
    return ss.str(); 
    } 

    double str2num(const std::string& s) 
    { 
    double d; 
    std::stringstream ss(s); 
    ss >> std::setprecision(std::numeric_limits<double>::max_digits10) >> d; 
    return d; 
    } 
} 

namespace from_string 
{ 
    std::string num2str(double d) 
    { 
    std::stringstream ss; 
    ss << std::setprecision(std::numeric_limits<double>::digits10) << d; 
    return ss.str(); 
    } 

    double str2num(const std::string& s) 
    { 
    double d; 
    std::stringstream ss(s); 
    ss >> std::setprecision(std::numeric_limits<double>::digits10) >> d; 
    return d; 
    } 
} 

int main() 
{ 
    double d = 1.34; 
    if (from_double::str2num(from_double::num2str(d)) == d) 
    std::cout << "Good for double -> string -> double" << std::endl; 
    else 
    std::cout << "Bad for double -> string -> double" << std::endl; 

    std::string s = "1.34"; 
    if (from_string::num2str(from_string::str2num(s)) == s) 
    std::cout << "Good for string -> double -> string" << std::endl; 
    else 
    std::cout << "Bad for string -> double -> string" << std::endl; 

    return 0; 
} 
Cuestiones relacionadas