Pythonで正確な小数計算を行う:decimalモジュールとDecimal型の正しい使い方

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) のように数値を直接渡すと、浮動小数点数の誤差がそのまま引き継がれるため避けるべきです。
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次