Python
 Computer >> コンピューター >  >> プログラミング >> Python

Pythonのdecimalモジュールを使った正確な10進数・浮動小数点演算の完全ガイド

Pythonにおける浮動小数点数の落とし穴

Pythonを含む多くのプログラミング言語では、浮動小数点数はメモリ上で基数2(バイナリ)の分数として表現されます。この仕組みが原因で、浮動小数点演算の結果はときに直感に反するものになります。

その典型例が、次のような計算です。

>>> 0.1 + 0.2
0.30000000000000004

「0.3」になるはずの計算結果に、見慣れない誤差が含まれています。これはPython特有のバグではなく、バイナリ浮動小数点表現の本質的な性質によるものであり、あらゆるプログラミング言語で共通して発生する現象です。

decimalモジュールとは

こうした問題を解決するために、Python標準ライブラリには decimal モジュールが用意されています。このモジュールは、高速かつ正確に丸められた10進浮動小数点演算を実現します。

decimalモジュールは、浮動小数点数を「本来あるべき挙動」どおりに表現できるよう設計されており、演算結果も人間の期待どおりの一貫したものになります。表現および演算の精度は最大28桁まで設定可能です。

Decimalオブジェクトの作成方法

decimalモジュールでは Decimal クラスが定義されています。コンストラクタには、整数・数値を表す文字列・タプルのいずれかを渡すことで、Decimalオブジェクトを生成できます。

>>> from decimal import Decimal
>>> d1 = Decimal(10)
>>> d1
Decimal('10')
>>> d2 = Decimal('10')
>>> d2
Decimal('10')

タプルによる指定

タプルを引数とする場合は、次の3つの要素を指定します。

  • 符号:正なら0、負なら1
  • 数字のタプル:各桁の数字
  • 指数

例を見てみましょう。

>>> d3 = Decimal((1, (1, 2, 3, 4), 2))
>>> d3
Decimal('-1.234E+5')

この場合、「符号:負」「数字:1234」「指数:2」と解釈され、-1.234×10⁵ という値になります。

getcontext()で精度を制御する

特定の精度を持つ浮動小数点数をより便利に扱う方法として、現在のスレッドのコンテキスト環境を getcontext() 関数で取得し、Decimalオブジェクトの精度(prec)を設定する方法があります。

>>> from decimal import Decimal, getcontext
>>> getcontext().prec = 5
>>> d3 = Decimal(10)
>>> d4 = Decimal(3)
>>> d3/d4
Decimal('3.3333')

ここでいうコンテキストとは、算術演算のための環境のことであり、以下の役割を担います。

  • 演算の精度(有効桁数)の決定
  • 丸めルールの定義
  • 指数部の範囲制限

主なコンテキスト操作関数

  • decimal.getcontext() — アクティブなスレッドの現在のコンテキストを返します。
  • decimal.setcontext(c) — アクティブなスレッドの現在のコンテキストを c に設定します。

丸めモードの一覧

decimalモジュールには、次の丸めモード定数が定義されています。

ROUND_CEILING+∞(無限大)方向へ丸める
ROUND_DOWNゼロ方向へ丸める
ROUND_FLOOR-∞(負の無限大)方向へ丸める
ROUND_HALF_DOWN近い方へ丸める(同値の場合はゼロ方向へ)
ROUND_HALF_EVEN近い方へ丸める(同値の場合は最も近い偶数へ)
ROUND_HALF_UP近い方へ丸める(同値の場合はゼロから遠い方向へ)
ROUND_UPゼロから遠い方向へ丸める
ROUND_05UPゼロ方向へ丸めた際の最後の桁が0または5になる場合はゼロから遠い方向へ、それ以外はゼロ方向へ丸める

精度と丸めモードを組み合わせた使用例

次のコードは、コンテキストオブジェクトの精度と丸めパラメータを実際に利用する例です。

>>> from decimal import *
>>> getcontext().prec = 5
>>> getcontext().rounding = ROUND_UP
>>> d1 = Decimal(100)
>>> d2 = Decimal(6)
>>> d1/d2
Decimal('16.667')

Decimalオブジェクトに対する算術演算

Decimalオブジェクトは、通常のfloat型とほぼ同様に、すべての一般的な算術演算を行うことができます。

>>> a = Decimal('2.4')
>>> b = Decimal('1.2')
>>> a + b
Decimal('3.6')
>>> a - b
Decimal('1.2')
>>> b - a
Decimal('-1.2')
>>> a * b
Decimal('2.88')
>>> a / b
Decimal('2')

Decimalと整数の混在は可能、floatとの混在は不可

Decimalオブジェクトは整数(int)との演算が可能です。しかし、通常の浮動小数点数(float)との直接演算は無効であり、TypeErrorが発生します。

>>> a = Decimal('2.4')
>>> c = 2.1
>>> a + c
Traceback (most recent call last):
File "<pyshell#37>", line 1, in <module>
a+c
TypeError: unsupported operand type(s) for +: 'decimal.Decimal' and 'float'

この例外は加算だけでなく、減算・乗算・除算などすべての算術演算で発生します。floatと演算したい場合は、事前に float を文字列化して Decimal に変換するなどの対応が必要です。

剰余演算子(%)の注意点

Decimalオブジェクトに対する剰余演算子(%)の挙動は、通常の数値型とは少し異なります。通常のint/floatでは結果の符号は除数(右側の値)に従いますが、Decimalでは被除数(左側の値)の符号が引き継がれます。

>>> -7 % 3
2
>>> 7 % -3
-2
>>> Decimal(-7) % Decimal(3)
Decimal('-1')
>>> Decimal(7) % Decimal(-3)
Decimal('1')

金額計算などで剰余を扱う場合は、この違いを必ず意識してください。

Decimal.from_float()の挙動

Decimal.from_float() は、通常のfloatをバイナリ表現そのままでDecimalオブジェクトに変換する関数です。そのため、from_float(0.1) の結果と Decimal('0.1') の結果は同じにはなりません

>>> d1 = Decimal('0.1')
>>> d2 = Decimal.from_float(0.1)
>>> d1, d2
(Decimal('0.1'), Decimal('0.1000000000000000055511151231257827021181583404541015625'))

このように、from_float()を使うとfloatが内部的に持つバイナリ近似誤差までそのまま引き継いでしまいます。正確な10進数値を扱いたい場合は、文字列からDecimalを生成するのが基本です。

まとめ

本記事では、Python標準ライブラリのdecimalモジュールが提供する機能について解説しました。

  • バイナリ浮動小数点の誤差問題(0.1 + 0.2 ≠ 0.3)
  • Decimalオブジェクトの生成方法(整数・文字列・タプル)
  • getcontext()による精度と丸めモードの制御
  • floatとの演算時のTypeErrorや剰余演算の符号規則の違い

金融計算や請求処理など、厳密な小数演算が求められる場面では、decimalモジュールの活用が非常に有効です。ぜひ実務でも活用してみてください。

  1. Pythonで現在の日付と時刻を取得する方法|datetimeモジュールとstrftimeの使い方

    はじめにPythonでは、現在の日付や時刻を取得する方法がいくつかあります。最も手軽なのが標準ライブラリのdatetimeモジュールを使う方法です。このモジュールにはnow()関数が用意されており、これを呼び出すだけで現在の日付と時刻を簡単に取得できます。datetimeモジュールで現在日時を取得する基本例import datetime now = datetime.datetime.now() print(Current date and time: ) print(str(now))実行結果上記のコードを実行すると、次のような出力が得られます。2017-12-29 11:24:48.042

  2. Pythonで浮動小数点数を固定幅にフォーマットする方法を解説

    文字列フォーマットで固定幅を指定する Pythonでは、文字列フォーマット機能を使うことで、浮動小数点数(float型)を任意の固定幅に整形して表示できます。たとえば、小数点の位置を揃えるために「全体の幅を12文字」「小数点以下を4桁」に指定したい場合は、format()メソッドを次のように使います。 >>> x = 12.35874 >>> print({:12.4f}.format(x)) 12.3587 書式指定 {:12.4f} のうち、「12」が出力全体の幅(文字数)、「.4f」が小数点以下4桁の浮動小数点数であることを意味します。デフォ