15. 浮動小数点演算:問題と制限—Pythonドキュメント

提供:Dev Guides
< PythonPython/docs/3.8/tutorial/floatingpoint
移動先:案内検索

15。 浮動小数点演算:問題と制限

浮動小数点数は、コンピューターハードウェアでは基数2(2進数)の分数として表されます。 たとえば、小数部

0.125

値は1/10 + 2/100 + 5/1000で、同じように2進数の分数です。

0.001

値は0/2 + 0/4 +1/8です。 これらの2つの分数の値は同じですが、実際の違いは、最初の分数が基数10の分数表記で記述され、2番目の分数が基数2で記述されていることだけです。

残念ながら、ほとんどの小数は2進分数として正確に表すことはできません。 結果として、一般に、入力する10進数の浮動小数点数は、実際にマシンに格納されている2進数の浮動小数点数でのみ概算されます。

この問題は、10進数で最初は理解しやすいです。 分数1/3を考えてみましょう。 これを基数10の分数として概算できます。

0.3

または、より良い、

0.33

または、より良い、

0.333

等々。 書き留めても構わないと思っている桁数に関係なく、結果は正確に1/3になることはありませんが、1/3の近似値はますます良くなります。

同様に、使用する基数2の桁数に関係なく、10進値0.1を基数2の小数として正確に表すことはできません。 基数2では、1/10は無限に繰り返される分数です

0.0001100110011001100110011001100110011001100110011...

任意の有限ビット数で停止すると、近似値が得られます。 今日のほとんどのマシンでは、浮動小数点数は、分子が最上位ビットから始まり、分母が2の累乗である最初の53ビットを使用する2進小数を使用して近似されます。 1/10の場合、2進小数は3602879701896397 / 2 ** 55であり、1/10の真の値に近いですが、正確には等しくありません。

多くのユーザーは、値の表示方法が原因で概算に気づいていません。 Pythonは、マシンによって保存された2進近似の真の10進値に対する10進近似のみを出力します。 ほとんどのマシンでは、Pythonが0.1で保存されたバイナリ近似の真の小数値を出力する場合、次のように表示する必要があります。

>>> 0.1
0.1000000000000000055511151231257827021181583404541015625

これは、ほとんどの人が役立つと思うよりも多くの桁数であるため、Pythonは、代わりに丸められた値を表示することにより、桁数を管理しやすくします。

>>> 1 / 10
0.1

印刷された結果が1/10の正確な値のように見えても、実際に格納された値は最も近い表現可能な2進小数であることを覚えておいてください。

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

歴史的に、Pythonプロンプトと組み込みの repr()関数は、有効数字17桁の0.10000000000000001を選択していました。 Python 3.1以降、Python(ほとんどのシステム)はこれらの中で最も短いものを選択し、0.1を表示できるようになりました。

これはまさにバイナリ浮動小数点の性質であることに注意してください。これはPythonのバグではなく、コードのバグでもありません。 ハードウェアの浮動小数点演算をサポートするすべての言語で同じ種類のものが表示されます(ただし、一部の言語では、デフォルトで、またはすべての出力モードで違いが表示されない場合があります)。

より快適な出力のために、文字列フォーマットを使用して、限られた数の有効数字を生成することをお勧めします。

>>> format(math.pi, '.12g')  # give 12 significant digits
'3.14159265359'

>>> format(math.pi, '.2f')   # give 2 digits after the point
'3.14'

>>> repr(math.pi)
'3.141592653589793'

これは、実際には幻想であるということを理解することが重要です。つまり、実際のマシン値の表示を単純に丸めているだけです。

ある幻想が別の幻想を生むかもしれません。 たとえば、0.1は正確に1/10ではないため、0.1の3つの値を合計しても、正確に0.3にならない場合があります。

>>> .1 + .1 + .1 == .3
False

また、0.1は1/10の正確な値に近づくことができず、0.3は3/10の正確な値に近づくことができないため、 round()関数で事前に丸めることは役に立ちません。

>>> round(.1, 1) + round(.1, 1) + round(.1, 1) == round(.3, 1)
False

数値を意図した正確な値に近づけることはできませんが、 round()関数は、不正確な値の結果が互いに比較できるように、丸め後の処理に役立ちます。

>>> round(.1 + .1 + .1, 10) == round(.3, 10)
True

バイナリ浮動小数点演算には、このような多くの驚きがあります。 「0.1」の問題については、以下の「表現エラー」セクションで詳しく説明します。 その他の一般的な驚きのより完全な説明については、浮動小数点の危険性を参照してください。

それが終わり近くに言うように、「簡単な答えはありません」。 それでも、浮動小数点に過度に注意しないでください。 Pythonのfloat演算のエラーは、浮動小数点ハードウェアから継承され、ほとんどのマシンでは、演算ごとに2 ** 53の1つの部分しかありません。 これはほとんどのタスクには十分すぎるほどですが、これは10進演算ではなく、すべてのfloat演算で新しい丸め誤差が発生する可能性があることに注意する必要があります。

病理学的なケースは存在しますが、浮動小数点演算のほとんどのカジュアルな使用では、最終結果の表示を期待する10進数に単純に丸めると、最終的に期待する結果が表示されます。 通常は str()で十分です。より詳細な制御については、 Format String Syntaxstr.format()メソッドのフォーマット指定子を参照してください。

正確な10進表現が必要なユースケースでは、会計アプリケーションや高精度アプリケーションに適した10進演算を実装する decimal モジュールを使用してみてください。

正確な算術の別の形式は、有理数に基づく算術を実装する fractions モジュールによってサポートされます(したがって、1/3のような数を正確に表すことができます)。

浮動小数点演算のヘビーユーザーである場合は、SciPyプロジェクトによって提供される数学および統計演算用の数値Pythonパッケージおよび他の多くのパッケージを確認する必要があります。 https://scipy.org >。

Pythonは、floatの正確な値を本当に実行したいというまれな場合に役立つツールを提供します。 float.as_integer_ratio()メソッドは、floatの値を分数として表します。

>>> x = 3.14159
>>> x.as_integer_ratio()
(3537115888337719, 1125899906842624)

比率は正確であるため、元の値をロスレスで再作成するために使用できます。

>>> x == 3537115888337719 / 1125899906842624
True

float.hex()メソッドは、floatを16進数(基数16)で表し、コンピューターに格納されている正確な値を示します。

>>> x.hex()
'0x1.921f9f01b866ep+1'

この正確な16進表現を使用して、float値を正確に再構築できます。

>>> x == float.fromhex('0x1.921f9f01b866ep+1')
True

表現は正確であるため、Pythonの異なるバージョン間で値を確実に移植し(プラットフォームに依存しない)、同じ形式をサポートする他の言語(JavaやC99など)とデータを交換するのに役立ちます。

もう1つの便利なツールは、 math.fsum()関数です。これは、合計中の精度の低下を軽減するのに役立ちます。 値が現在の合計に追加されるときに、「失われた数字」を追跡します。 これにより、全体的な精度に違いが生じ、エラーが最終的な合計に影響を与えるほど蓄積されないようにすることができます。

>>> sum([0.1] * 10) == 1.0
False
>>> math.fsum([0.1] * 10) == 1.0
True

15.1。 表現エラー

このセクションでは、「0.1」の例について詳しく説明し、このようなケースを自分で正確に分析する方法を示します。 バイナリ浮動小数点表現の基本的な知識があることを前提としています。

表現エラーは、一部の(ほとんどの場合)10進数の分数を2進数(基数2)の分数として正確に表現できないという事実を指します。 これが、Python(またはPerl、C、C ++、Java、Fortran、およびその他の多く)が期待する正確な10進数を表示しないことが多い主な理由です。

何故ですか? 1/10は、2進分数として正確に表現できるわけではありません。 今日(2000年11月)のほとんどすべてのマシンはIEEE-754浮動小数点演算を使用しており、ほとんどすべてのプラットフォームがPython浮動小数点数をIEEE-754「倍精度」にマップしています。 754ダブルには53ビットの精度が含まれているため、入力時に、コンピューターは0.1を J / 2 ** N の形式で可能な最も近い分数に変換しようとします。ここで、 J [ X169X]は、正確に53ビットを含む整数です。 書き換え

1 / 10 ~= J / (2**N)

なので

J ~= 2**N / 10

J は正確に53ビット(>= 2**52ですが< 2**53)であることを思い出すと、 N の最適な値は56です。

>>> 2**52 <=  2**56 // 10  < 2**53
True

つまり、56は N の唯一の値であり、 J に正確に53ビットを残します。 J の可能な最良の値は、その商を四捨五入したものです。

>>> q, r = divmod(2**56, 10)
>>> r
6

余りは10の半分以上であるため、切り上げによって最良の近似値が得られます。

>>> q+1
7205759403792794

したがって、754倍精度で1/10に可能な限り最良の近似は次のとおりです。

7205759403792794 / 2 ** 56

分子と分母の両方を2で割ると、分数は次のようになります。

3602879701896397 / 2 ** 55

切り上げたので、これは実際には1/10より少し大きいことに注意してください。 切り上げていなかったとしたら、商は1/10より少し小さかったでしょう。 しかし、決して正確に 1/10になることはできません!

したがって、コンピュータは1/10を「認識」することはありません。認識されるのは、上記の正確な分数であり、取得できる最高の754二重近似です。

>>> 0.1 * 2 ** 55
3602879701896397.0

その分数に10 ** 55を掛けると、小数点以下55桁までの値がわかります。

>>> 3602879701896397 * 10 ** 55 // 2 ** 55
1000000000000000055511151231257827021181583404541015625

これは、コンピューターに格納されている正確な数値が10進値0.1000000000000000055511151231257827021181583404541015625に等しいことを意味します。 多くの言語(古いバージョンのPythonを含む)では、完全な10進値を表示する代わりに、結果を有効数字17桁に丸めます。

>>> format(0.1, '.17f')
'0.10000000000000001'

fractions および decimal モジュールを使用すると、次の計算が簡単になります。

>>> from decimal import Decimal
>>> from fractions import Fraction

>>> Fraction.from_float(0.1)
Fraction(3602879701896397, 36028797018963968)

>>> (0.1).as_integer_ratio()
(3602879701896397, 36028797018963968)

>>> Decimal.from_float(0.1)
Decimal('0.1000000000000000055511151231257827021181583404541015625')

>>> format(Decimal.from_float(0.1), '.17')
'0.10000000000000001'