15. 浮動小数点演算、その問題と制限
**********************************

浮動小数点数は、計算機ハードウェアの中では、基数を 2 とする (2進法の)
分数として表現されています。例えば、小数

   0.125

は、 1/10 + 2/100 + 5/1000 という値を持ちますが、これと同様に、2 進法
の分数

   0.001

は 0/2 + 0/4 + 1/8 という値になります。これら二つの分数は同じ値を持っ
ていますが、ただ一つ、最初の分数は基数 10 で記述されており、二番目の分
数は基数 2 で記述されていることが違います。

残念なことに、ほとんどの小数は 2 進法の分数として正確に表わすことがで
きません。その結果、一般に、入力した 10 進の浮動小数点数は、 2 進法の
浮動小数点数で近似された後、実際にマシンに記憶されます。

最初は基数 10 を使うと問題を簡単に理解できます。分数 1/3 を考えてみま
しょう。分数 1/3 は、基数 10 の分数として、以下のように近似することが
できます:

   0.3

さらに正確な近似は、

   0.33

さらに正確な近似は、

   0.333

となり、以後同様です。何個桁数を増やして書こうが、結果は決して厳密な
1/3 にはなりません。しかし、少しづつ正確な近似にはなっていくでしょう。

同様に、基数を 2 とした表現で何桁使おうとも、10 進数の 0.1 は基数を 2
とした小数で正確に表現することはできません。基数 2 では、1/10 は循環小
数 (repeating fraction) となります

   0.0001100110011001100110011001100110011001100110011...

どこか有限の桁で止めると、近似値を得ることになります。近年の殆どのコン
ピュータでは float 型は、最上位ビットから数えて最初の 53 ビットを分子
、2 の冪乗を分母とした、二進小数で近似されます。1/10 の場合は、二進小
数は "3602879701896397 / 2 ** 55" となります。これは、1/10 に近いです
が、厳密に同じ値ではありません。

値が表示される方法のために、ほとんどのユーザは、近似に気づきません。
Python はマシンに格納されている二進近似値の10進小数での近似値を表示す
るので、格納されている値が元の10進小数の近似値でしか無いことを忘れがち
です。ほとんどのマシンで、もし Python が2進数で近似された 0.1 の近似値
をそのまま10進数で表示していたら、その結果は次のようになったでしょう

   >>> 0.1
   0.1000000000000000055511151231257827021181583404541015625

これは、ほとんどの人が必要と感じるよりも多すぎる桁数です。なので、
Python は丸めた値を表示することで、桁数を扱いやすい範囲にとどめます

   >>> 1 / 10
   0.1

表示された結果が正確に 1/10 であるように見えたとしても、実際に格納され
ている値は最も近く表現できる二進小数であるということだけは覚えておいて
ください。

幾つかの異なる10進数の値が、同じ2進有理数の近似値を共有しています。例
えば、"0.1" と "0.10000000000000001" と
"0.1000000000000000055511151231257827021181583404541015625" はどれも
"3602879701896397 / 2 ** 55" に近似されます。同じ近似値を共有している
ので、どの10進数の値も "eval(repr(x)) == x" という条件を満たしたまま同
じように表示されます。

昔の Python は、プロンプトと "repr()" ビルトイン関数は 17 桁の有効数字
を持つ "0.10000000000000001" のような10進数の値を選んで表示していまし
た。 Python 3.1 からは、ほとんどの場面で "0.1" のような最も短い桁数の
10進数の値を選ぶようになりました。

この動作は2進数の浮動小数点にとってはごく自然なものです。これは Python
のバグではありませんし、あなたのコードのバグでもありません。ハードウェ
アの浮動小数点演算をサポートしている全ての言語で同じ種類の問題を見つけ
ることができます (いくつかの言語ではデフォルトの、あるいはどの出力モー
ドを選んでも、この差を **表示** しないかもしれませんが)。

よりよい出力のために、文字列フォーマットを利用して有効桁数を制限した10
進数表現を得ることができます:

   >>> 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 ではないために
、それを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

2 進の浮動小数点数に対する算術演算は、このような意外性をたくさん持って
います。 "0.1" に関する問題は、以下の "表現エラー" の章で詳細に説明し
ます。 2 進法の浮動小数点演算にともなうその他のよく知られた意外な事象
に関しては The Perils of Floating Point を参照してください。

究極的にいうと、"容易な答えはありません"。ですが、浮動小数点数のことを
過度に警戒しないでください！ Python の float 型操作におけるエラーは浮
動小数点処理ハードウェアから受けついたものであり、ほとんどのマシン上で
は一つの演算あたり高々 2**53 分の 1 です。この誤差はほとんどの作業で充
分以上のものですが、浮動小数点演算は 10 進の演算ではなく、浮動小数点の
演算を新たに行うと、新たな丸め誤差の影響を受けることを心にとどめておい
てください。

異常なケースが存在する一方で、普段の浮動小数点演算の利用では、単に最終
的な結果の値を必要な 10 進の桁数に丸めて表示するのなら、最終的には期待
通りの結果を得ることになるでしょう。たいては "str()" で十分ですが、き
め細かな制御をしたければ、 書式指定文字列の文法 にある "str.format()"
メソッドのフォーマット仕様を参照してください。

正確な10進数表現が必要となるような場合には、 "decimal" モジュールを利
用してみてください。このモジュールは会計アプリケーションや高精度の計算
が求められるアプリケーションに適した、10進数の計算を実装しています。

別の正確な計算方法として、 "fractions" モジュールが有理数に基づく計算
を実装しています (1/3 のような数を正確に表すことができます)。

あなたが浮動小数点演算のヘビーユーザーなら、SciPy プロジェクトが提供し
ている NumPy パッケージやその他の数学用パッケージを調べてみるべきです
。 <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進数で表現します。この値もコン
ピューターが持っている正確な値を表現できます:

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

この正確な16進数表現はもとの float 値を正確に復元するために使うことが
できます:

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

この16進数表現は正確なので、値を (プラットフォームにも依存せず) バージ
ョンの異なるPython 間でやり取りしたり、他のこのフォーマットをサポート
した言語 (Java や C99 など) と正確にやり取りするのに利用することができ
ます。

別の便利なツールとして、合計処理における精度のロスを緩和してくれる
"math.fsum()" 関数があります。この関数は値を合計値に足し込みながら、 "
失われた桁" を管理します。これにより、誤差が最終的な合計値に影響を与え
るまで蓄積されなくなり、結果が改善されます:

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


15.1. 表現エラー
================

この章では、"0.1" の例について詳細に説明し、このようなケースに対してど
のようにすれば正確な分析を自分で行えるかを示します。ここでは、 2 進法
表現の浮動小数点数についての基礎的な知識があるものとして話を進めます。

表現エラー(*Representation error*)は、いくつかの (実際にはほとんどの)
10 進の小数が 2 進法 (基数 2)の分数として表現できないという事実に関係
しています。これは Python (あるいは Perl, C, C++, Java, Fortran. およ
びその他多く) が期待通りの正確な 10 進数を表示できない主要な理由です。

なぜこうなるのでしょうか？ 1/10 は 2 進法の小数で厳密に表現することが
できません。今日 (2000年11月) のマシンは、ほとんどすべて IEEE-754 浮動
小数点演算を使用しており、ほとんどすべてのプラットフォームでは Python
の浮動小数点を IEEE-754 における "倍精度(double precision)" に対応付け
ます。754 の double には 53 ビットの精度を持つ数が入るので、計算機に入
力を行おうとすると、可能な限り 0.1 を最も近い値の分数に変換し、
*J*/2***N* の形式にしようと努力します。*J* はちょうど 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 は *J* をちょうど 53 ビットの精度のままに保つ *N* の唯一
の値です。*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'
