letras - Convertir un Float de Python en una Cadena sin perder precisión
float en python (5)
Estoy manteniendo una secuencia de comandos de Python que usa xlrd
para recuperar los valores de las hojas de cálculo de Excel y luego hago varias cosas con ellos. Algunas de las celdas en la hoja de cálculo son números de alta precisión, y deben permanecer como tales. Al recuperar los valores de una de estas celdas, xlrd
me da un float
como 0.38288746115497402.
Sin embargo, necesito poner este valor en una cadena más adelante en el código. Hacer cualquier str(value)
o unicode(value)
devolverá algo así como "0,382887461155". Los requisitos dicen que esto no es aceptable; la precisión necesita ser preservada.
Hasta ahora he probado un par de cosas sin éxito. El primero estaba usando una cosa de formato de cadena:
data = "%.40s" % (value)
data2 = "%.40r" % (value)
Pero ambos producen el mismo número redondeado, "0.382887461155".
Después de buscar personas con problemas similares en SO y en otras partes de Internet, una sugerencia común fue utilizar la clase Decimal
. Pero no puedo cambiar la forma en que se me dan los datos (a menos que alguien sepa de una manera secreta de hacer xlrd
return Decimales). Y cuando trato de hacer esto:
data = Decimal(value)
Obtengo un TypeError: Cannot convert float to Decimal. First convert the float to a string.
TypeError: Cannot convert float to Decimal. First convert the float to a string.
Pero obviamente no puedo convertirlo en una cuerda, de lo contrario perderé la precisión.
Así que sí, estoy abierto a cualquier sugerencia, incluso si es realmente grosera / hacky. No tengo mucha experiencia con Python (más un tipo Java / C #) así que siéntete libre de corregirme si tengo algún tipo de malentendido fundamental aquí.
EDITAR: Solo pensé que agregaría que estoy usando Python 2.6.4. No creo que haya ningún requisito formal que me impida cambiar las versiones; simplemente no debe estropear ninguno de los otros códigos.
Como ya se ha dicho, una flotación no es precisa en absoluto, por lo que preservar la precisión puede ser algo engañoso.
Esta es una forma de obtener hasta el último bit de información de un objeto flotante:
>>> from decimal import Decimal
>>> str(Decimal.from_float(0.1))
''0.1000000000000000055511151231257827021181583404541015625''
Otra forma sería así.
>>> 0.1.hex()
''0x1.999999999999ap-4''
Ambas cadenas representan el contenido exacto del flotador. Casi todo lo demás interpreta el flotador, ya que Python cree que probablemente fue intencionado (lo que la mayoría de las veces es correcto).
Puede usar repr()
para convertir a una cadena sin perder precisión, luego convierta a Decimal:
>>> from decimal import Decimal
>>> f = 0.38288746115497402
>>> d = Decimal(repr(f))
>>> print d
0.38288746115497402
Soy el autor de xlrd. Hay tanta confusión en otras respuestas y comentarios para refutar en los comentarios, así que lo estoy haciendo en una respuesta.
@katriealex: "" "precisión que se pierde en las entrañas de xlrd" "" --- completamente infundada y falsa. xlrd reproduce exactamente el flotante de 64 bits que está almacenado en el archivo XLS.
@katriealex: "" "Puede ser posible modificar la instalación xlrd local para cambiar el modelo flotante" "" --- No sé por qué querrías hacer esto; ¡¡no pierdas precisión flotando un entero de 16 bits !!! En cualquier caso, ese código se usa solo cuando se leen archivos Excel 2.X (que tenían un registro de celda tipo INTEGER). El OP no da ninguna indicación de que esté leyendo archivos tan antiguos.
@jloubert: Debes estar equivocado. "%.40r" % a_float
es solo una forma barroca de obtener la misma respuesta que repr(a_float)
.
@EVERYBODY: no necesita convertir un flotador en un decimal para conservar la precisión. El objetivo de la función repr()
es que se garantiza lo siguiente:
float(repr(a_float)) == a_float
Python 2.X (X <= 6) repr da una constante de 17 dígitos decimales de precisión, ya que está garantizado que reproduce el valor original. Las pitones posteriores (2.7, 3.1) dan la cantidad mínima de dígitos decimales que reproducirán el valor original.
Python 2.6.4 (r264:75708, Oct 26 2009, 08:23:19) [MSC v.1500 32 bit (Intel)] on win32
>>> f = 0.38288746115497402
>>> repr(f)
''0.38288746115497402''
>>> float(repr(f)) == f
True
Python 2.7 (r27:82525, Jul 4 2010, 09:01:59) [MSC v.1500 32 bit (Intel)] on win32
>>> f = 0.38288746115497402
>>> repr(f)
''0.382887461154974''
>>> float(repr(f)) == f
True
Entonces, la conclusión es que si quieres una cadena que preserve toda la precisión de un objeto flotante, usa preserved = repr(the_float_object)
... recupera el valor más tarde mediante float(preserved)
. Es así de simple. No es necesario el módulo decimal
.
EDITAR: Estoy equivocado. Dejaré esta respuesta aquí para que el resto del hilo tenga sentido, pero no es verdad. Por favor, vea la respuesta de John Machin arriba. Gracias chicos =).
Si las respuestas anteriores funcionan, eso es genial, te ahorrará muchos ataques maliciosos. Sin embargo, al menos en mi sistema, no lo harán. Puede verificar esto con, por ejemplo,
import sys
print( "%.30f" % sys.float_info.epsilon )
Ese número es el flotante más pequeño que su sistema puede distinguir de cero. Cualquier cosa más pequeña que eso se puede agregar o restar aleatoriamente de cualquier flotador cuando realiza una operación. Esto significa que, al menos en mi configuración de Python, la precisión se pierde dentro de las entrañas de xlrd
, y parece que no hay nada que puedas hacer sin modificarla. Que es extraño; Esperaba que este caso hubiera ocurrido antes, ¡pero aparentemente no!
Es posible modificar la instalación de xlrd
local para cambiar el modelo float
. Abra site-packages/xlrd/sheet.py
y vaya a la línea 1099:
...
elif rc == XL_INTEGER:
rowx, colx, cell_attr, d = local_unpack(''<HH3sH'', data)
self_put_number_cell(rowx, colx, float(d), self.fixed_BIFF2_xfindex(cell_attr, rowx, colx))
...
Fíjate en el reparto float
: puedes intentar cambiar eso a un decimal.Decimal
y ver qué pasa.
EDITAR: borró mi respuesta anterior b / c no funcionó correctamente.
Estoy en Python 2.6.5 y esto funciona para mí:
a = 0.38288746115497402
print repr(a)
type(repr(a)) #Says it''s a string
Nota: Esto simplemente se convierte en una cadena. Tendrá que convertir a Decimal
usted mismo más adelante si es necesario.