home changes contents help options

188:浮動小数点数を比較する

差をとって丸める

浮動小数点数の演算には各種の誤差がつきものなので単に == で比較すると思ったような結果になりません。:

   >>> a = 0.1 + 0.2
   >>> b = 0.3
   >>> a == b
   False

現実的な対策としては、差をとり任意の桁数で丸めて 0 と比較するとよいでしょう。 round 関数を用いて小数点以下 7 桁で丸めてみます。:

   >>> a = 0.1 + 0.2
   >>> b = 0.3
   >>> round(a-b, 7) == 0
   True

ちなみに、筆者の環境で各値を小数点以下 35 桁まで表示すると次のようになりました。:

   >>> '%.35f' % a
   '0.30000000000000004000000000000000000'
   >>> '%.35f' % b
   '0.29999999999999999000000000000000000'
   >>> '%.35f' % (a - b)
   '0.00000000000000005551115123125782700'
   >>> '%.35f' % round(a - b, 7)
   '0.00000000000000000000000000000000000'

参考

Unittest.py より TestCase クラスの failUnlessAlmostEqual メソッド。

first と second を places で与えた小数位で値を丸めて差分を計算し、ゼロと比較することで、近似的に等価であるかどうかをテストします。:

    def failUnlessAlmostEqual(self, first, second, places=7, msg=None):
        if round(second-first, places) != 0:
            raise self.failureException, \
                  (msg or '%r != %r within %r places' % (first, second, places))

レシピを書くにあたりパクっ…参考にしています

Decimal - 10 進浮動小数点数

レシピの趣旨に反しますが。 2 進浮動小数点数の 2 進ゆえのまるめ誤差に悩まされたくなければ 10 進浮動小数点数を用いるという手段があります。 decimal モジュールの Decimal クラスがそうです。:

    >>> from decimal import Decimal
    >>> a = Decimal('0.1') + Decimal('0.2')
    >>> b = Decimal('0.3')
    >>> a == b
    True

日ごろ用いている算数がほぼ通用するのはありがたいものです。とはいえ、有限の浮動小数点数には違いないので 1 / 3 といった無限小数をうまく表現できるわけではありませんし、精度を超えた情報の保持も不可能です。 比較の際、精度内での計算に始終していたのでなければ 2 進浮動小数点数同様の注意が必要です。:

    >>> Decimal(1) == 1
    True
    >>> Decimal(1) / Decimal(3)
    Decimal("0.3333333333333333333333333333")
    >>> Decimal(1) / Decimal(3) * Decimal(3) == 1  # 1/3*3 と 1 は 10 進浮動小数点数でも等しくならない
    False
    >>>
    >>> x = Decimal(10000000000000000)
    >>> y = Decimal('0.000000000000001')
    >>> x == x + y  # 加えた値と桁が違いすぎて無視される
    True

なお、 decimal モジュールでは計算精度、算術コンテキスト、シグナルを Python プログラマ側で制御できるところも魅力となっています。 詳しくは ライブラリリファレンス decimal を参照してください。

有理数

有理数を用いることで 1 / 3 などの正確な表現が可能となります。 192:有理数を使う を参照してください。