Python 3では4 * 0.1の浮動小数点値は見栄えが良いのに、3 * 0.1は見栄えが悪いのはなぜですか?


158

ほとんどの小数には正確な浮動小数点表現がないことがわかっています(浮動小数点演算は壊れていますか?)。

しかし、両方の値が実際に醜い10進数表現を持っている場合、なぜ4*0.1がとしてうまく表示されるのかわかりません0.4が、そうで3*0.1はありません。

>>> 3*0.1
0.30000000000000004
>>> 4*0.1
0.4
>>> from decimal import Decimal
>>> Decimal(3*0.1)
Decimal('0.3000000000000000444089209850062616169452667236328125')
>>> Decimal(4*0.1)
Decimal('0.40000000000000002220446049250313080847263336181640625')

7
正確に表現できるものもあれば、表現できないものもあります。
モーガンスラップ

58
@MorganThrapp:いいえ、そうではありません。OPは、かなり恣意的に見えるフォーマットの選択について尋ねています。0.3も0.4も2進浮動小数点で正確に表すことはできません。
バトシェバ2016

42
@BartoszKP:Pythonが表示される理由は、ドキュメントを何度も読んだ、それは説明していない0.30000000000000004440892098500626161694526672363281250.30000000000000004し、0.40000000000000002220446049250313080847263336181640625.4彼らは同じ精度を持っているように見えるにもかかわらず、したがって、質問に答えていません。
Mooing Duck 2016

6
stackoverflow.com/questions/28935257/…も参照してください。-重複としてクローズされたのに少しイライラしましたが、これはクローズされていません。
Random832

12
再度開かれました"is floating point math broken"の複製として閉じないください
Antti Haapala 2016

回答:


301

単純な答えは、3*0.1 != 0.3量子化(丸め)エラーが原因です(4*0.1 == 0.42のべき乗を掛けることは通常「正確な」演算であるため)。

.hexPythonでこのメソッドを使用して、数値の内部表現(基本的には、10進数の近似値ではなく、正確な 2進浮動小数点値)を表示できます。これは、内部で何が起こっているのかを説明するのに役立ちます。

>>> (0.1).hex()
'0x1.999999999999ap-4'
>>> (0.3).hex()
'0x1.3333333333333p-2'
>>> (0.1*3).hex()
'0x1.3333333333334p-2'
>>> (0.4).hex()
'0x1.999999999999ap-2'
>>> (0.1*4).hex()
'0x1.999999999999ap-2'

0.1は0x1.999999999999a x 2 ^ -4です。末尾の「a」は数字の10を意味します。つまり、2進浮動小数点の0.1は、「正確な」値の0.1よりもわずかに大きくなります(最後の0x0.99は0x0.aに切り上げられるため)。これに4の2のべき乗を掛けると、指数は上にシフトします(2 ^ -4から2 ^ -2へ)が、数値はそれ以外は変わらないので、4*0.1 == 0.4です。

ただし、3を掛けると、0x0.99と0x0.a0(0x0.07)のわずかな違いが0x0.15エラーに拡大され、最後の位置に1桁のエラーとして表示されます。これにより、0.1 * 3 は、丸められた値0.3よりもわずかに大きくなります。

Python 3のfloat reprは、ラウンドトリップが可能なように設計されています。つまり、表示される値は、元の値に正確に変換できる必要があります。したがって、それは表示できない0.30.1*3まったく同じように、または2つの異なる番号がラウンドトリップ後も同じことになります。その結果、Python 3のreprエンジンは、わずかな明らかなエラーがあるものを表示することを選択します。


25
これは驚くほど包括的な答えです、ありがとう。(特に、見せてくれてありがとう.hex()。存在することは知りませんでした。)
NPE

21
@supercat:Pythonは、目的の値に丸める最短の文字列を見つけようとします。明らかに、評価された値は0.5ulp以内である必要があります(または他の値に丸められます)が、あいまいな場合はさらに桁が必要になることがあります。コードは非常にぎこちないですが、覗き見をしたい場合:hg.python.org/cpython/file/03f2c8fc24ea/Python/dtoa.c#l2345
nneonneo

2
@supercat:常に0.5 ulp以内の最も短い文字列。(奇数のLSBを持つフロートを見ている場合、厳密に言えば、つまり、丸め結合から偶数結合で動作するようにする最短の文字列)。これに対する例外はバグであり、報告する必要があります。
マークディキンソン

7
@MarkRansom確かに彼らはそれがeすでに16 進数であるため、それ以外のものを使用しました。たぶん指数の代わりに力のpために。
ベルギ2016

11
@Bergi:pこのコンテキストでのの使用は(少なくとも)C99にまで遡り、IEEE 754およびその他のさまざまな言語(Javaを含む)でも使用されます。ときfloat.hexfloat.fromhex実施された(私が:-)、Pythonは単にそれまでの練習を設立したものをコピーしました。意図が「パワー」の「p」であったかどうかはわかりませんが、それを考える良い方法のように思えます。
マークディキンソン

75

repr(およびstrPython 3では)値を明確にするために必要な桁数を出力します。この場合、乗算の結果は3*0.10.3(16進数で0x1.3333333333333p-2)に最も近い値ではなく、実際には1 LSB高い(0x1.3333333333334p-2)ため、0.3と区別するためにさらに桁が必要です。

一方、乗算で4*0.1 0.4(16進数で0x1.999999999999ap-2)に最も近い値取得されるため、追加の数字は必要ありません。

これは非常に簡単に確認できます。

>>> 3*0.1 == 0.3
False
>>> 4*0.1 == 0.4
True

上記の16進表記を使用したのは、2値間のビットの違いがわかりやすくコンパクトであるためです。これは、egを使用して自分で行うことができます(3*0.1).hex()。十代の栄光でそれらを見たい場合は、ここに行きます:

>>> Decimal(3*0.1)
Decimal('0.3000000000000000444089209850062616169452667236328125')
>>> Decimal(0.3)
Decimal('0.299999999999999988897769753748434595763683319091796875')
>>> Decimal(4*0.1)
Decimal('0.40000000000000002220446049250313080847263336181640625')
>>> Decimal(0.4)
Decimal('0.40000000000000002220446049250313080847263336181640625')

2
(+1)いい答えです、ありがとう。3*0.1 == 0.3and の結果を含めることで、「最も近い値ではない」ポイントを説明する価値があると思います4*0.1 == 0.4か?
NPE

@NPE提案してくれてありがとう。
Mark Ransom

多くの人は浮動小数点の16進数を読み取ることができないため、0.1、0.3、0.4に最も近い「倍精度浮動小数点数」の正確な10進値に注目する価値があるのでしょうか。
スーパーキャット2016

@supercatあなたは良い点を作ります。それらの超大型のダブルスをテキストに入れるのは邪魔になるでしょうが、私はそれらを追加する方法を考えました。
Mark Ransom

25

他の回答からの簡単な結論は次のとおりです。

Pythonのコマンドラインでフロートをチェックするか、印刷すると、repr文字列表現を作成する関数を通過します。

バージョン3.2以降、Python は複雑な丸めスキームstrrepr使用します。これは、可能であれば見栄えのよい小数を優先しますが、フロートとその文字列表現間の全単射(1対1)マッピングを保証するために必要な場合はより多くの桁を使用します。

このスキームはrepr(float(s))、たとえ浮動小数点数として正確に表現できない場合でも(例:when s = "0.1")

同時に、それfloat(repr(x)) == xはすべてのフロートを保持することを保証しますx


2
あなたの答えは、Pythonバージョン> = 3.2で正確です。ここでstr、とreprfloatは同じです。Python 2.7の場合、reprは特定するプロパティを備えていますが、strはるかに単純です。12桁の有効数字を計算し、それらに基づいて出力文字列を生成するだけです。Python <= 2.6の場合、reprstrは両方とも、有効数字の固定数に基づいています(は17、reprは12 str)。(そして誰もPython 3.0やPython 3.1を気にしません:-)
Mark Dickinson

@MarkDickinson、ありがとう!回答にあなたのコメントを含めました。
Aivar 2016

2
シェルから丸めが来ることに注意してくださいreprこれはPython 2.7の動作は同じになります...
アンティHaapala

5

実際にはPythonの実装に固有ではありませんが、浮動小数点から10進数の文字列関数に適用する必要があります。

浮動小数点数は基本的に2進数ですが、有効数字の固定制限を持つ科学表記法です。

基数と共有されていない素数の因数を持つ任意の数の逆は、常に繰り返しドットポイント表現になります。たとえば、1/7には素因数7があり、10と共有されないため、繰り返し10進数表現があり、素数因数2と5の1/10にも同じことが当てはまり、後者は2と共有されません。 ; つまり、0.1はドット点の後の有限ビット数で正確に表すことはできません。

0.1には正確な表現がないため、近似を小数点文字列に変換する関数は通常、0.1000000000004121のような直感的でない結果が得られないように特定の値を近似しようとします。

浮動小数点は科学表記であるため、底のべき乗による乗算は、数値の指数部にのみ影響します。たとえば、10進表記の場合は1.231e + 2 * 100 = 1.231e + 4であり、同様に、2進表記の場合は1.00101010e11 * 100 = 1.00101010e101です。ベースの非べき乗を掛けると、有効数字も影響を受けます。たとえば、1.2e1 * 3 = 3.6e1

使用されるアルゴリズムによっては、有効数字のみに基づいて一般的な小数を推測しようとする場合があります。0.1と0.4はどちらも、2進数で同じ有意な数値を持っています。これは、それらの浮動小数点数が本質的にそれぞれ(8/5)(2 ^ -4)と(8/5)(2 ^ -6)の切り捨てであるためです。アルゴリズムが8/5 sigfigパターンを小数1.6として識別した場合、0.1、0.2、0.4、0.8などで機能します。float3をfloat 10で除算するなど、他の組み合わせのマジックsigfigパターンもある場合があります。統計的に10で割ることによって形成される可能性が高い他の魔法のパターン

3 * 0.1の場合、最後の数桁の有効数字は、浮動小数点数3を浮動小数点数10で除算したものとは異なる可能性があり、精度の損失に対する許容度に応じて、アルゴリズムは0.3定数のマジック番号を認識できません。

編集:https : //docs.python.org/3.1/tutorial/floatingpoint.html

興味深いことに、同じ最も近い2進小数を共有する多くの異なる10進数があります。たとえば、0.1と0.10000000000000001と0.1000000000000000055511151231257827021181583404541015625はすべて3602879701896397/2 ** 55で近似されます。これらの10進値はすべて同じ近似値を共有するため、不変のeval(repr(x)を維持しながら、それらのいずれか1つを表示できます。 )== x。

精度の低下は許容されません。floatx(0.3)がfloat y(0.1 * 3)と完全に等しくない場合、repr(x)はrepr(y)と完全には等しくありません。


4
これは実際には既存の回答に多くを追加しません。
Antti Haapala 2016

1
「使用されるアルゴリズムによっては、有効数字のみに基づいて一般的な小数を推測しようとする場合があります。」<-これは純粋な推測のようです。他の回答では、Pythonが実際に行うことを説明しています。
マークディキンソン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.