web-dev-qa-db-ja.com

精度を失うことなくPython Floatを文字列に変換する

xlrdを使用してExcelスプレッドシートから値を取得し、それらを使用してさまざまな処理を行うPythonスクリプトを維持しています。スプレッドシートの一部のセルは、高精度の数値です。これらのセルの1つの値を取得すると、xlrdは0.38288746115497402などのfloatを返します。

ただし、コードの後半でこの値を文字列に取り込む必要があります。 str(value)またはunicode(value)のいずれかを実行すると、「0.382887461155」のようなものが返されます。要件は、これは受け入れられないと言っています。精度を維持する必要があります。

私はこれまでにいくつかのことを試しましたが、成功しませんでした。 1つ目は、文字列フォーマットを使用することでした。

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

ただし、どちらも同じ丸められた数値「0.382887461155」を生成します。

SOやインターネット上の他の場所で同様の問題を抱えている人々を探し回ったとき、一般的な提案はDecimalクラスを使用することでした。しかし、データの方法を変更することはできません。 (誰かがxlrdに小数を返す秘密の方法を知らない限り)そして私がこれをやろうとすると:

data = Decimal(value)

TypeError: Cannot convert float to Decimal. First convert the float to a string.しかし、明らかに文字列に変換できません。そうしないと、精度が失われます。

そうそう、私はどんな提案にもオープンです-必要ならば本当にひどい/ハッキーなものでさえ。私はPython(Java/C#の人の方が多い)の経験があまりないので、ここで何らかの根本的な誤解がある場合は、遠慮なく訂正してください。

編集:私が使用していることを追加すると思っただけですPython 2.6.4。バージョンの変更を妨げる正式な要件はないと思います。それは、いずれかを台無しにする必要はありません。他のコード。

27
jloubert

私はxlrdの作者です。他の回答やコメントには混乱があり、コメントで反論するので、私は回答でそれを行っています。

@katriealex: "" "xlrdの内臓で精度が失われている" "" ---まったく根拠がなく真実ではありません。 xlrdは、XLSファイルに格納されている64ビット浮動小数点数を正確に再現します。

@katriealex: "" "ローカルのxlrdインストールを変更してフロートキャストを変更できる可能性があります" "" ---なぜこれを実行するのかわかりません。 16ビット整数を浮動させても精度が失われることはありません!!!いずれの場合も、そのコードは、Excel 2.Xファイル(INTEGERタイプのセルレコードが含まれている)を読み取る場合にのみ使用されます。 OPは、彼がそのような古代のファイルを読んでいることを示していません。

@jloubert:あなたは間違っているに違いありません。 _"%.40r" % a_float_は、repr(a_float)と同じ答えを得るバロック的な方法です。

@EVERYBODY:精度を維持するためにfloatを10進数に変換する必要はありません。 repr()関数の要点は、次のことが保証されているということです。

_float(repr(a_float)) == a_float
_

Python 2.X(X <= 6)reprは、元の値を再現することが保証されているため、定数の10進数の17桁の精度を提供します。後のPython(2.7、3.1)は、元の値を再現する最小の10進数を提供します。

_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
_

つまり、要点はfloatオブジェクトのすべての精度を保持する文字列が必要な場合は、preserved = repr(the_float_object)を使用します...後でfloat(preserved)によって値を回復します。それはとても簡単です。 decimalモジュールは必要ありません。

50
John Machin

repr()を使用して、精度を失うことなく文字列に変換してから、Decimalに変換できます。

>>> from decimal import Decimal
>>> f = 0.38288746115497402
>>> d = Decimal(repr(f))
>>> print d
0.38288746115497402
2
eldarerathis

編集:私は間違っています。スレッドの残りの部分が意味をなすように、この回答をここに残しておきますが、それは真実ではありません。上記のJohn Machinの回答を参照してください。ありがとうございます=)

上記の答えがうまく機能すれば、それは素晴らしいことです-それはあなたに多くの厄介なハッキングを救うでしょう。ただし、少なくとも私のシステムでは、そうではありません。あなたは例えばでこれをチェックすることができます.

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

その数は、システムがゼロと区別できる最小のフロートです。それよりも小さいものは、操作を実行するときにフロートからランダムに加算または減算される場合があります。 これは、少なくとも私のPythonセットアップでは、精度はxlrdの内臓内で失われ、それを変更せずにできることは何もないように見えることを意味します。これは奇妙なことに、私はこの事件が以前に起こったと思っていたが、明らかにそうではなかった!

ローカルのxlrdインストールを変更して、floatキャストを変更できる場合があります。 site-packages\xlrd\sheet.pyを開き、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))
...

floatキャストに注意してください。これをdecimal.Decimalに変更して、何が起こるかを確認できます。

1
Katriel

すでに述べたように、フロートはまったく正確ではありません。そのため、精度を維持することは多少誤解を招く可能性があります。

Floatオブジェクトから最後のすべての情報を取得する方法は次のとおりです。

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

別の方法はそうなるでしょう。

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

両方の文字列は、フロートの正確な内容を表します。他のほとんどすべては、フロートをpythonはおそらく意図されたものだと考えています(ほとんどの場合正しい)と解釈します。

0
Stefano Palazzo

編集:以前の回答をクリアしましたが、正しく機能しませんでした。

私はPython 2.6.5を使用していますが、これは私にとってはうまくいきます:

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

注:これは文字列に変換されるだけです。必要に応じて、後で自分でDecimalに変換する必要があります。

0
avacariu