Conversion D'un flotteur Python en chaîne sans perte de précision

Je maintiens un script Python qui utilise xlrd pour récupérer des valeurs à partir de feuilles de calcul Excel, puis faire diverses choses avec eux. Certaines des cellules de la feuille de calcul sont des nombres de haute précision, et elles doivent rester telles quelles. Lors de la récupération des valeurs de l'une de ces cellules, xlrd me donne un float tel que 0.38288746115497402.

Cependant, j'ai besoin d'obtenir cette valeur dans une chaîne plus tard dans le code. Faire str(value) ou unicode(value) retournera quelque chose comme "0.382887461155". Le les exigences disent que cela n'est pas acceptable; la précision doit être préservée.

J'ai essayé quelques choses jusqu'à présent sans succès. Le premier utilisait un truc de formatage de chaîne:

data = "%.40s" % (value) 
data2 = "%.40r" % (value) 

Mais les deux produisent le même nombre arrondi, "0.382887461155".

Lors de la recherche de personnes ayant des problèmes similaires sur SO et ailleurs sur internet, une suggestion commune était d'utiliser la classe Decimal. Mais je ne peux pas changer la façon dont les données me sont données (à moins que quelqu'un connaît un moyen secret de faire xlrd retour décimales). Et quand j'essaie de faire ceci:

data = Decimal(value)

J'obtiens un {[9] } mais évidemment je ne peux pas le convertir en chaîne, sinon je perdrai la précision.

Donc oui, je suis ouvert à toutes les suggestions-même celles vraiment grossières / hacky si nécessaire. Je ne suis pas très expérimenté avec Python (plus D'un gars Java/C# moi-même) alors n'hésitez pas à me corriger si j'ai une sorte de malentendu fondamental ici.

EDIT: je pensais juste ajouter que J'utilise python 2.6.4. Je ne pense pas qu'il y ait des exigences formelles qui m'empêchent de changer de version; il suffit de ne pas gâcher l'autre code.

25
demandé sur jloubert 2010-08-14 03:33:41

5 réponses

Je suis l'auteur de xlrd. Il y a tellement de confusion dans les autres réponses et commentaires à réfuter dans les commentaires, donc je le fais dans une réponse.

@katriealex: """la précision étant perdue dans les entrailles de xlrd """ - - - entièrement infondé et faux. xlrd reproduit exactement le flottant 64 bits stocké dans le fichier XLS.

@katriealex: """il peut être possible de modifier votre installation xlrd locale pour changer le cast float """ - - - Je ne sais pas pourquoi vous voudriez faire cela; vous ne perdez pas précision en flottant un entier de 16 bits!!! Dans tous les cas, ce code est utilisé uniquement lors de la lecture D'Excel 2.X fichiers (qui avaient un enregistrement de cellule de type entier). Le PO ne donne aucune indication qu'il lit des fichiers aussi anciens.

@jloubert: vous devez Vous tromper. "%.40r" % a_float est juste un baroque façon d'obtenir la même réponse que repr(a_float).

@ EVERYBODY: vous n'avez pas besoin de convertir un flottant en décimal pour préserver la précision. Le point entier de la fonction repr() est que ce qui suit est garantie:

float(repr(a_float)) == a_float

Python 2.X (X

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

Donc, la ligne du bas est que si vous voulez une chaîne qui préserve toute la précision d'un objet float, Utilisez preserved = repr(the_float_object) ... récupérer la valeur plus tard par float(preserved)., C'est aussi simple que cela. Pas besoin du module decimal.

46
répondu John Machin 2010-08-14 07:40:32

Vous pouvez utiliser repr() pour convertir en chaîne sans perdre de précision, puis convertir en décimal:

>>> from decimal import Decimal
>>> f = 0.38288746115497402
>>> d = Decimal(repr(f))
>>> print d
0.38288746115497402
2
répondu eldarerathis 2010-08-14 01:53:29

EDIT: je me trompe. Je vais laisser cette réponse ici pour que le reste du fil ait du sens, mais ce n'est pas vrai. Veuillez consulter la réponse de John Machin ci-dessus. Merci les gars =).

Si les réponses ci-dessus fonctionnent, c'est génial-cela vous évitera beaucoup de piratage méchant. Cependant, au moins sur mon système, ils ne le feront pas. vous pouvez vérifier cela avec par exemple

import sys
print( "%.30f" % sys.float_info.epsilon )

ce nombre est le plus petit flotteur que votre système peut distinguer de zéro. Quelque chose de plus petit que cela peut être aléatoire ajouté ou soustrait de n'importe quel flotteur lorsque vous effectuez une opération. cela signifie que, au moins sur ma configuration Python, la précision est perdue dans les entrailles de xlrd, et il semble n'y avoir rien que vous puissiez faire sans la modifier. Ce qui est étrange; je m'attendais à ce que cette affaire se soit produite auparavant, mais apparemment non!

Il peut être possible de modifier votre installation xlrd locale pour changer la distribution float. Ouvrez site-packages\xlrd\sheet.py et descendez à la ligne 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))
...

Notez la distribution float -- vous pouvez essayer de changer cela en decimal.Decimal et voir ce qui se passe.

1
répondu Katriel 2010-08-14 12:24:19

EDIT: effacé ma réponse précédente b / c cela n'a pas fonctionné correctement.

Je suis sur Python 2.6.5 et cela fonctionne pour moi:

a = 0.38288746115497402
print repr(a)
type(repr(a))    #Says it's a string

Note: cela se convertit simplement en une chaîne. Vous devrez vous convertir en Decimal plus tard si nécessaire.

0
répondu avacariu 2010-08-14 00:40:19

Comme cela a déjà été dit, un flotteur n'est pas précis du tout - donc préserver la précision peut être quelque peu trompeur.

Voici un moyen d'obtenir toutes les dernières informations d'un objet float:

>>> from decimal import Decimal
>>> str(Decimal.from_float(0.1))
'0.1000000000000000055511151231257827021181583404541015625'

Une autre façon serait comme ça.

>>> 0.1.hex()
'0x1.999999999999ap-4'

Les deux chaînes représentent le contenu exact du flottant. Tout le reste interprète le float comme python pense qu'il était probablement destiné (ce qui est la plupart du temps correct).

0
répondu Stefano Palazzo 2010-08-14 15:50:13