Conversion d'un flotteur Python à une chaîne sans perte de précision
-
28-09-2019 - |
Question
Je suis maintenant un script Python que les utilisations xlrd
pour récupérer les valeurs à partir des feuilles de calcul Excel, puis faire des choses différentes avec eux. Certaines des cellules dans la feuille de calcul sont des nombres de haute précision, et ils doivent le rester. Lors de la récupération des valeurs d'une de ces cellules, xlrd
me donne un float
tel que ,38288746115497402.
Cependant, je dois obtenir cette valeur en une chaîne plus tard dans le code. Faire soit str(value)
ou unicode(value)
retournera quelque chose comme « 0,382887461155 ». Les exigences disent que ce n'est pas acceptable; la précision doit être préservée.
J'ai essayé deux choses jusqu'ici sans succès. La première est une chaîne en utilisant le formatage thingy:
data = "%.40s" % (value)
data2 = "%.40r" % (value)
Mais les deux produisent le même nombre arrondi, « 0,382887461155 ».
Lors de la recherche autour des 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 est donnée (à moins que quelqu'un sait d'une façon secrète pour faire DECIMALS retour de xlrd
). Et quand je tente de faire ceci:
data = Decimal(value)
Je reçois un TypeError: Cannot convert float to Decimal. First convert the float to a string.
Mais je ne peux évidemment pas le convertir en une chaîne, ou bien je vais perdre la précision.
Alors oui, je suis ouvert à toute suggestion - même vraiment les bruts / aki si nécessaire. Je ne suis pas terriblement expérimenté avec Python (plus d'un type Java / C # moi-même) ne hésitez pas à me corriger si j'ai une sorte de malentendu fondamental.
EDIT: Juste pensé que je voudrais ajouter que je suis en utilisant Python 2.6.4. Je ne pense pas qu'il y ait des exigences formelles me arrêtant des versions changeantes; il a juste de ne pas gâcher une de l'autre code.
La solution
Je suis l'auteur de xlrd. Il y a tellement de confusion dans d'autres réponses et commentaires à réfuter dans les commentaires, donc je le fais dans une réponse.
@katriealex: « » « précision se perdre dans les entrailles de xlrd » « » --- dénuées de tout fondement et faux. xlrd reproduit exactement le flotteur 64 bits qui est stocké dans le fichier XLS.
@katriealex: « » « Il est possible de modifier votre installation de xlrd local pour changer la distribution float » « » --- Je ne sais pas pourquoi vous voulez le faire; vous ne perdez pas de précision par un entier flottant 16 bits !!! En tout cas, ce code est utilisé uniquement lors de la lecture des fichiers Excel 2.x (qui avait un dossier de cellules de type ENTIER). L'OP ne donne aucune indication qu'il est en train de lire ces fichiers anciens.
@jloubert: Vous devez vous tromper. "%.40r" % a_float
est juste une façon baroque d'obtenir la même réponse que repr(a_float)
.
@EVERYBODY: Vous n'avez pas besoin de convertir un flotteur à une décimale pour préserver la précision. Le point de l'ensemble de la fonction repr()
est que le suivant est garanti:
float(repr(a_float)) == a_float
Python 2.X (X <= 6) repr donne une constante de 17 chiffres décimaux de précision, comme cela est garanti pour reproduire la valeur d'origine. Plus tard Pythons (2.7, 3.1) donne le nombre minimal de chiffres décimaux qui reproduit la valeur d'origine.
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
Ainsi, la ligne de fond est que si vous voulez une chaîne qui conserve toute la précision d'un objet flottant, utilisez preserved = repr(the_float_object)
... récupérer la valeur plus tard par float(preserved)
. Il est aussi simple que cela. Pas besoin du module decimal
.
Autres conseils
Vous pouvez utiliser repr()
pour convertir en une chaîne sans perte de précision, puis convertir en décimal:
>>> from decimal import Decimal
>>> f = 0.38288746115497402
>>> d = Decimal(repr(f))
>>> print d
0.38288746115497402
EDIT: Je me trompe. Je quitterai cette réponse ici pour que le reste du fil est logique, mais il est pas vrai. S'il vous plaît voir la réponse de John Machin ci-dessus. Merci les gars =).
Si les réponses ci-dessus fonctionnent c'est génial - il vous fera économiser beaucoup de hacking méchant. Cependant, au moins sur mon système, ils ne seront pas. Vous pouvez le vérifier avec par exemple.
import sys
print( "%.30f" % sys.float_info.epsilon )
Ce numéro est le plus petit flotteur que votre système peut distinguer de zéro. Tout ce plus petit que celui peut être aléatoire ajouté ou soustrait de tout flotteur lorsque vous effectuez une opération. Cela signifie que, au moins sur ma configuration Python, la précision est perdu dans les entrailles de xlrd
, et il semble y avoir rien vous pouvez le faire sans le modifier. Ce qui est étrange; Je l'aurais prévu ce cas ait eu lieu avant, mais apparemment pas!
Il est possible de modifier votre installation de xlrd
local pour changer la distribution de 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 de float
-. Vous pouvez essayer de changer cela à un decimal.Decimal
et voir ce qui se passe
EDIT:. Effacé ma réponse précédente b / c il 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: Cette seulement convertit à une chaîne. Vous devrez convertir pour vous Decimal
plus tard si nécessaire.
Comme il a été dit, un flotteur est pas précis du tout -. Préservant ainsi la précision peut être quelque peu trompeur
Voici une façon d'obtenir chaque bit d'information sur un objet flottant:
>>> from decimal import Decimal
>>> str(Decimal.from_float(0.1))
'0.1000000000000000055511151231257827021181583404541015625'
Une autre façon serait comme si.
>>> 0.1.hex()
'0x1.999999999999ap-4'
Les deux chaînes représentent le contenu exact du flotteur. Allmost quoi que ce soit d'autre interprète le flotteur comme python pense qu'il a probablement été conçu (dont la plupart du temps est correct).