Pythonの標準的な浮動小数点数(float型)は、内部的に2進数で値を表現しているため、0.1 のような単純な小数であっても正確に表現できず、計算時に微細な「誤差」が発生することがあります。
金融計算や科学技術計算など、わずかな誤差も許されない厳密な計算が必要な場面では、標準ライブラリの decimal モジュールを使用する必要があります。
この記事では、Decimal 型を使った正確な計算方法と、インスタンス生成時にやってはいけない「アンチパターン」について解説します。
浮動小数点数(float)の計算誤差
まず、float 型で計算した場合にどのような問題が起こるかを確認します。例えば、0.1 を3回足すと 0.3 になるはずですが、float では以下のようになります。
# float型での計算
val_float = 0.1 + 0.1 + 0.1
print(f"計算結果: {val_float}")
print(f"0.3と等しいか: {val_float == 0.3}")
実行結果:
計算結果: 0.30000000000000004
0.3と等しいか: False
この誤差は、コンピュータが小数を2進数で近似表現していることに起因します。
Decimal 型による正確な計算
decimal モジュールの Decimal クラスを使用すると、数値を10進数として正確に扱い、このような誤差を回避できます。
正しい使い方:文字列で初期化する
Decimal オブジェクトを作成する際は、引数に数値を**「文字列」**として渡すのが鉄則です。
from decimal import Decimal
# 文字列として値を渡す
d1 = Decimal("0.1")
d2 = Decimal("0.1")
d3 = Decimal("0.1")
# Decimal同士で計算
total_decimal = d1 + d2 + d3
print(f"計算結果: {total_decimal}")
print(f"Decimal('0.3')と等しいか: {total_decimal == Decimal('0.3')}")
実行結果:
計算結果: 0.3
Decimal('0.3')と等しいか: True
期待通り 0.3 となり、等価判定も True になりました。
具体的な使用例:通貨の計算
例えば、「10.00ドルから9.95ドルを引く」という計算を行います。これも float では誤差が出やすい計算の一つです。
from decimal import Decimal
# floatの場合
price_float = 10.00
cost_float = 9.95
print(f"floatの計算結果: {price_float - cost_float}")
# Decimalの場合(文字列で初期化)
price_dec = Decimal("10.00")
cost_dec = Decimal("9.95")
result_dec = price_dec - cost_dec
print(f"Decimalの計算結果: {result_dec}")
実行結果:
floatの計算結果: 0.049999999999998934
Decimalの計算結果: 0.05
Decimal を使うことで、正確に 0.05 という結果が得られました。
注意点:float値で初期化してはいけない
Decimal を使う際、最も陥りやすい間違いが、コンストラクタに float 型の数値をそのまま渡してしまうことです。
float 型の数値(例: 0.1)は、プログラムの中に書かれた時点で既に「誤差を含んだ近似値」になっています。これを Decimal に渡すと、その誤差を含んだ値をそのまま高精度に保持してしまいます。
# 悪い例: float型で初期化
# "0.1" ではなく、0.1の近似値がそのまま格納されてしまう
bad_decimal = Decimal(0.1)
print(f"文字列で作成: {Decimal('0.1')}")
print(f"数値で作成: {bad_decimal}")
実行結果:
文字列で作成: 0.1
数値で作成: 0.1000000000000000055511151231257827021181583404541015625
このように、数値で渡すと意図しない値になってしまいます。リテラル(直書き)の場合は必ずクォーテーションで囲み、変数から変換する場合は str() 関数で一度文字列に変換してから Decimal に渡すようにします。
# 変数に入ったfloat値を変換する場合
input_val = 0.1
safe_decimal = Decimal(str(input_val)) # 一度文字列にする
まとめ
- お金や精密な計測値など、誤差が許されない計算には
decimalモジュールを使用します。 Decimalオブジェクトを作成する際は、必ずDecimal("0.1")のように文字列を引数に渡します。Decimal(0.1)のように数値を直接渡すと、浮動小数点数の誤差がそのまま引き継がれるため避けるべきです。
