Python / numpy precisión de texto en coma flotante

Digamos que tengo algunos valores de punto flotante de 32 bits y 64 bits:

>>> import numpy as np >>> v32 = np.array([5, 0.1, 2.4, 4.555555555555555, 12345678.92345678635], dtype=np.float32) >>> v64 = np.array([5, 0.1, 2.4, 4.555555555555555, 12345678.92345678635], dtype=np.float64) 

Quiero serializar estos valores a texto sin perder precisión (o al menos realmente cerca de no perder precisión). Creo que la forma canónica de hacer esto es con repr :

 >>> map(repr, v32) ['5.0', '0.1', '2.4000001', '4.5555553', '12345679.0'] >>> map(repr, v64) ['5.0', '0.10000000000000001', '2.3999999999999999', '4.5555555555555554', '12345678.923456786'] 

Pero quiero que la representación sea lo más compacta posible para minimizar el tamaño del archivo, por lo que sería bueno que valores como 2.4 se serializaran sin los decimales adicionales. Sí, sé que esa es su representación real de punto flotante, pero %g parece poder encargarse de esto:

 >>> ('%.7g ' * len(v32)) % tuple(v32) '5 0.1 2.4 4.555555 1.234568e+07 ' >>> ('%.16g ' * len(v32)) % tuple(v64) '5 0.1 2.4 4.555555555555555 12345678.92345679 ' 

Mi pregunta es: ¿es seguro usar %g de esta manera? ¿Son .7 y .16 los valores correctos para que la precisión no se pierda?

Python 2.7 y versiones posteriores ya tienen una implementación de reproducción inteligente para flotadores que se imprime 0.1 como 0.1 . La salida breve se elige de preferencia a otros candidatos, como 0.10000000000000001 porque es la representación más corta de ese número en particular que hace un recorrido al mismo valor de punto flotante exacto cuando se vuelve a leer en Python. Para usar este algoritmo, convierta sus flotadores de 64 bits a flotadores de Python reales antes de entregarlos para que repr :

 >>> map(repr, map(float, v64)) ['5.0', '0.1', '2.4', '4.555555555555555', '12345678.923456786'] 

Sorprendentemente, el resultado es de apariencia natural y numéricamente correcto. Puede encontrar más información sobre 2.7 / 3.2 repr en Novedades y una fascinante conferencia de Mark Dickinson.

Desafortunadamente, este truco no funcionará para flotadores de 32 bits, al menos no sin volver a implementar el algoritmo utilizado por la repr Python 2.7.

Para determinar de manera única un número de punto flotante de precisión simple (32 bits) en formato IEEE-754, puede ser necesario usar 9 (significativo, es decir, no comenzar con 0, a menos que el valor sea 0) dígitos decimales, y 9 dígitos son siempre suficiente

Para los números de punto flotante de doble precisión (64 bits), 17 dígitos decimales (significativos) pueden ser necesarios y siempre son suficientes.

No estoy muy seguro de cómo se especifica el formato %g , por su aspecto, puede permitir que la representación comience con un 0 (0.1), por lo que los valores seguros para la precisión serían .9 y .17 .

Si desea minimizar el tamaño del archivo, escribir las representaciones de bytes produciría un archivo mucho más pequeño, por lo que si puede hacerlo, ese es el camino a seguir.

El código C que implementa la reproducción de fantasía en 2.7 se encuentra principalmente en Python / dtoa.c (con envoltorios en Python / pystrtod.c y Objects / floatobject.c). En particular, mire _Py_dg_dtoa. Debería ser posible tomar prestado este código y modificarlo para que funcione con float en lugar de doble. Luego, puede envolver esto en un módulo de extensión, o simplemente construirlo como tal y ctypes.

Además, tenga en cuenta que la fuente dice que la implementación está “Inspirada en” Cómo imprimir con precisión los números de punto flotante “por Guy L. Steele, Jr. y Jon L. White [Proc. ACM SIGPLAN ’90, pp. 112-126] . ” Por lo tanto, puede ser capaz de implementar algo menos flexible y más simple al leer ese documento (y cualquiera de las modificaciones documentadas en los comentarios de dtoa.c parece apropiada).

Finalmente, el código es un cambio menor al código publicado por David Gay en AT&T, y se usa en otras bibliotecas (NSPR, etc.), una de las cuales podría tener una versión más accesible.

Pero antes de hacer nada de eso, asegúrese de que realmente hay un problema de rendimiento al probar una función de Python y medir si es demasiado lento.

Y si esto realmente es un área de rendimiento crítico, probablemente no desee recorrer la lista y llamar a repr (o su propia función de C) en primer lugar; Probablemente desee una función que convierta una serie de flotadores o se duplique en una cadena, todo a la vez. (Por supuesto, lo ideal sería que lo construyas en números).

Un último pensamiento: estás buscando “al menos muy cerca de no perder precisión”. Es posible que simplemente convertir al doble y usar la reproducción sea lo suficientemente cerca para tus propósitos, y obviamente es mucho más fácil que cualquier otra cosa, por lo que al menos deberías probarlo para descartarlo.

No hace falta decir que también debe comprobar si %.9g y %.17g están lo suficientemente cerca para sus propósitos, ya que esa es la siguiente cosa más fácil que podría funcionar.