Pythonで計算していて、こんな不思議な結果に出会ったことはありませんか?
print(0.1 + 0.2)
# 結果: 0.30000000000000004
# あれ?0.3じゃないの?
これは浮動小数点誤差という、コンピューターの仕組み上避けられない問題です。普通の計算なら気にならないかもしれませんが、お金の計算や精密な測定では大問題になります。
「レジで100円と200円の商品を買ったら、合計が300.0000001円になった」なんて、とても困りますよね。
そんな問題を解決してくれるのが、Pythonのdecimalモジュールです。これを使えば、小数の計算でも正確な結果が得られます。
この記事でわかること
- 浮動小数点誤差がなぜ起こるのか
- decimalモジュールの基本的な使い方
- 実際にdecimalを使う方法
- floatとdecimalの使い分け
こんな人におすすめ
- お金の計算を扱うシステムを作る人
- 精密な数値計算が必要な人
- Pythonの数値処理をもっと理解したい人
- 浮動小数点誤差に悩まされている人
なぜ浮動小数点誤差が起こるの?

コンピューターの数値表現の仕組み
2進数での表現の限界: コンピューターは内部ですべてを2進数(0と1)で表現します。しかし、10進数の「0.1」を2進数で正確に表すことはできません。
身近な例で理解:
- 1/3を小数で表すと「0.33333…」と無限に続く
- 同じように、0.1を2進数で表すと無限に続いてしまう
- コンピューターは限られた桁数で近似するしかない
なぜ問題になるの?:
# 一見正しそうに見える計算
price1 = 0.1 # 100円の1割引(10円)
price2 = 0.2 # 200円の1割引(20円)
total = price1 + price2 # 合計の割引額
print(f"割引合計: {total}円")
# 結果: 割引合計: 0.30000000000000004円
浮動小数点誤差が問題になる場面
金融・会計システム:
- 銀行の利息計算
- 税額の計算
- 請求書の金額計算
- 家計簿アプリ
科学技術計算:
- 測定データの処理
- 統計計算
- 物理計算
- 品質管理
ビジネスアプリケーション:
- ECサイトの価格計算
- ポイント計算
- 割引率の計算
- 在庫管理
実際にどんな問題が起こるか
例1:消費税計算での問題
# 問題のあるコード
item_price = 100
tax_rate = 0.1 # 10%
tax = item_price * tax_rate
total = item_price + tax
print(f"税込み価格: {total}円")
# 結果: 税込み価格: 110.00000000000001円
例2:割引計算での問題
# 30%割引の計算
original_price = 1000
discount_rate = 0.3
discount_amount = original_price * discount_rate
final_price = original_price - discount_amount
print(f"割引後価格: {final_price}円")
# 結果: 割引後価格: 700.0000000000001円
これらの問題を解決するために、decimalモジュールが必要になります。
decimalモジュールとは?
decimalモジュールの特徴
正確な10進数計算:
- 10進数での正確な表現
- 浮動小数点誤差なし
- 人間が理解しやすい数値処理
カスタマイズ可能な精度:
- 小数点以下の桁数を指定可能
- 丸め方法を選択可能
- 計算精度を用途に応じて調整
豊富な数学操作:
- 基本的な四則演算
- べき乗、平方根
- 対数計算
- 三角関数(一部)
decimalモジュールが解決する問題
問題解決の例:
従来のfloat:
print(0.1 + 0.2)
# 結果: 0.30000000000000004
decimalを使用:
from decimal import Decimal
a = Decimal('0.1')
b = Decimal('0.2')
print(a + b)
# 結果: 0.3
いつdecimalを使うべき?
必ず使うべき場面:
- お金の計算
- 税率の計算
- 精密な測定値の処理
- 会計・簿記システム
floatでも十分な場面:
- 科学計算(近似で十分)
- グラフィック処理
- 機械学習(一般的には)
- ゲーム開発
判断の基準: 「1円でも誤差があったら困る」→ decimal 「多少の誤差は許容できる」→ float
次の章では、実際にdecimalモジュールの使い方を学んでいきましょう。
decimalモジュールの基本的な使い方
インポートと基本的な作成
モジュールのインポート:
from decimal import Decimal
Decimalオブジェクトの作成:
正しい方法(文字列を使用):
# 推奨: 文字列から作成
price = Decimal('10.50')
tax_rate = Decimal('0.1')
discount = Decimal('5.25')
print(price) # 10.50
print(tax_rate) # 0.1
print(discount) # 5.25
間違った方法(floatを使用):
# 非推奨: floatから作成
price = Decimal(10.50)
print(price) # 10.4999999999999982236431605997495353221893310546875
# → floatの誤差がそのまま持ち込まれる
なぜ文字列を使うの?:
- 文字列なら正確にその値を表現できる
- floatを使うと、すでに誤差が含まれている
- 「見たままの値」を確実に設定できる
基本的な四則演算
加算・減算・乗算・除算:
from decimal import Decimal
# 商品価格の計算例
item1 = Decimal('100.50') # 商品1
item2 = Decimal('200.25') # 商品2
tax_rate = Decimal('0.1') # 消費税10%
# 小計
subtotal = item1 + item2
print(f"小計: {subtotal}円") # 小計: 300.75円
# 消費税
tax = subtotal * tax_rate
print(f"消費税: {tax}円") # 消費税: 30.075円
# 合計
total = subtotal + tax
print(f"合計: {total}円") # 合計: 330.825円
割引計算の例:
# 20%割引の計算
original_price = Decimal('1000')
discount_rate = Decimal('0.2')
discount_amount = original_price * discount_rate
final_price = original_price - discount_amount
print(f"元の価格: {original_price}円")
print(f"割引額: {discount_amount}円")
print(f"最終価格: {final_price}円")
# 結果:
# 元の価格: 1000円
# 割引額: 200円
# 最終価格: 800円
比較演算
大小比較:
price1 = Decimal('100.50')
price2 = Decimal('100.25')
if price1 > price2:
print(f"{price1}円の方が高い")
else:
print(f"{price2}円の方が高い")
# 結果: 100.50円の方が高い
等価比較:
# 正確な比較ができる
a = Decimal('0.1') + Decimal('0.2')
b = Decimal('0.3')
print(a == b) # True(正確に等しい)
# floatだと...
c = 0.1 + 0.2
d = 0.3
print(c == d) # False(誤差のため等しくない)
文字列変換と表示
基本的な文字列変換:
price = Decimal('123.456')
# そのまま表示
print(str(price)) # 123.456
# f-stringでの表示
print(f"価格: {price}円") # 価格: 123.456円
フォーマットを指定した表示:
price = Decimal('123.456')
# 小数点以下2桁で表示
print(f"価格: {price:.2f}円") # 価格: 123.46円
# 小数点以下を0で埋める
print(f"価格: {price:08.2f}円") # 価格: 00123.46円
型変換
他の型への変換:
price = Decimal('123.45')
# intに変換(小数点以下切り捨て)
price_int = int(price)
print(price_int) # 123
# floatに変換(精度が落ちる可能性あり)
price_float = float(price)
print(price_float) # 123.45
注意点:
- intへの変換は小数点以下が切り捨てられる
- floatへの変換は精度が落ちる可能性がある
- 可能な限りDecimal型のまま計算を続ける
これで基本的な使い方はマスターできました。次は、より高度な丸め処理について学びましょう。
丸め処理と精度の詳細設定

基本的な丸め処理
quantize()メソッドの使用:
説明:指定した精度で丸め処理を行う
基本的な使い方:
from decimal import Decimal
price = Decimal('123.456')
# 小数点以下2桁に丸める
rounded_price = price.quantize(Decimal('0.01'))
print(rounded_price) # 123.46
様々な精度での丸め:
value = Decimal('123.456789')
# 整数に丸める
print(value.quantize(Decimal('1'))) # 123
# 小数点以下1桁
print(value.quantize(Decimal('0.1'))) # 123.5
# 小数点以下2桁
print(value.quantize(Decimal('0.01'))) # 123.46
# 小数点以下3桁
print(value.quantize(Decimal('0.001'))) # 123.457
丸め方法の指定
利用可能な丸めモード:
from decimal import Decimal, ROUND_HALF_UP, ROUND_DOWN, ROUND_UP, ROUND_HALF_EVEN
value = Decimal('2.5')
# 四捨五入(一般的な丸め)
result1 = value.quantize(Decimal('1'), rounding=ROUND_HALF_UP)
print(f"四捨五入: {result1}") # 3
# 切り捨て
result2 = value.quantize(Decimal('1'), rounding=ROUND_DOWN)
print(f"切り捨て: {result2}") # 2
# 切り上げ
result3 = value.quantize(Decimal('1'), rounding=ROUND_UP)
print(f"切り上げ: {result3}") # 3
# 銀行式丸め(偶数丸め)
result4 = value.quantize(Decimal('1'), rounding=ROUND_HALF_EVEN)
print(f"銀行式丸め: {result4}") # 2
丸め方法の詳細説明:
丸めモード | 説明 | 例(2.5の場合) | 用途 |
---|---|---|---|
ROUND_HALF_UP | 四捨五入 | 3 | 一般的な計算 |
ROUND_DOWN | 切り捨て | 2 | 価格表示 |
ROUND_UP | 切り上げ | 3 | 安全率を見込む計算 |
ROUND_HALF_EVEN | 銀行式丸め | 2 | 金融計算(推奨) |
実用的な丸め処理の例
消費税計算での丸め:
from decimal import Decimal, ROUND_DOWN
# 商品価格
item_price = Decimal('198')
tax_rate = Decimal('0.10') # 10%
# 消費税を計算(1円未満切り捨て)
tax = (item_price * tax_rate).quantize(Decimal('1'), rounding=ROUND_DOWN)
total = item_price + tax
print(f"商品価格: {item_price}円")
print(f"消費税: {tax}円")
print(f"税込み価格: {total}円")
# 結果:
# 商品価格: 198円
# 消費税: 19円 (19.8円を切り捨て)
# 税込み価格: 217円
ポイント還元率の計算:
from decimal import Decimal, ROUND_HALF_UP
purchase_amount = Decimal('1234')
point_rate = Decimal('0.05') # 5%還元
# ポイントを計算(小数点以下四捨五入)
points = (purchase_amount * point_rate).quantize(Decimal('1'), rounding=ROUND_HALF_UP)
print(f"購入金額: {purchase_amount}円")
print(f"還元ポイント: {points}ポイント")
# 結果:
# 購入金額: 1234円
# 還元ポイント: 62ポイント (61.7ポイントを四捨五入)
全体的な精度設定
getcontext()による設定:
説明:計算全体のデフォルト設定を変更
精度の設定:
from decimal import getcontext
# 現在の設定を確認
print(getcontext())
# 精度を10桁に設定
getcontext().prec = 10
# 計算実行
result = Decimal('1') / Decimal('3')
print(result) # 0.3333333333
丸め方法のデフォルト設定:
from decimal import getcontext, ROUND_HALF_UP
# デフォルトの丸め方法を四捨五入に設定
getcontext().rounding = ROUND_HALF_UP
# この設定は以降のすべての計算に適用される
value = Decimal('2.5')
rounded = value.quantize(Decimal('1')) # 丸め方法を指定しなくても四捨五入
print(rounded) # 3
一時的な設定変更
localcontext()の使用:
説明:一時的に設定を変更して、ブロック終了後に元に戻す
from decimal import Decimal, getcontext, localcontext, ROUND_DOWN
# 通常の設定
print("通常の精度:", getcontext().prec)
with localcontext() as ctx:
# 一時的に精度を変更
ctx.prec = 5
ctx.rounding = ROUND_DOWN
result = Decimal('1') / Decimal('3')
print(f"一時的な設定での結果: {result}") # 0.33333
# 元の設定に戻る
result2 = Decimal('1') / Decimal('3')
print(f"元の設定での結果: {result2}")
これで丸め処理の基本はマスターできました。次は、floatとの違いをより詳しく見ていきましょう。
floatとdecimalの詳細比較
精度と表現の違い
floatの限界:
IEEE 754浮動小数点標準:
- 64ビットで数値を表現
- 精度は約15〜17桁
- 2進数での近似表現
decimalの利点:
10進数での正確な表現:
- 任意の精度で計算可能
- 10進数での厳密な表現
- 人間の直感と一致する結果
実際の比較例:
精度の比較:
import math
from decimal import Decimal, getcontext
# floatの場合
float_pi = math.pi
print(f"float π: {float_pi}")
# float π: 3.141592653589793
# decimalの場合(精度を50桁に設定)
getcontext().prec = 50
decimal_pi = Decimal('3.1415926535897932384626433832795028841971693993751')
print(f"decimal π: {decimal_pi}")
# decimal π: 3.1415926535897932384626433832795028841971693993751
計算精度の違い:
# float
a_float = 0.1
b_float = 0.2
c_float = 0.3
print(f"float: {a_float + b_float} == {c_float} は {a_float + b_float == c_float}")
# float: 0.30000000000000004 == 0.3 は False
# decimal
a_decimal = Decimal('0.1')
b_decimal = Decimal('0.2')
c_decimal = Decimal('0.3')
print(f"decimal: {a_decimal + b_decimal} == {c_decimal} は {a_decimal + b_decimal == c_decimal}")
# decimal: 0.3 == 0.3 は True
パフォーマンスの比較
速度の違い:
測定コード:
import time
from decimal import Decimal
# float での計算
start = time.time()
total_float = 0.0
for i in range(100000):
total_float += 0.1
float_time = time.time() - start
# decimal での計算
start = time.time()
total_decimal = Decimal('0')
decimal_point_one = Decimal('0.1')
for i in range(100000):
total_decimal += decimal_point_one
decimal_time = time.time() - start
print(f"float処理時間: {float_time:.4f}秒")
print(f"decimal処理時間: {decimal_time:.4f}秒")
print(f"decimal は float の約{decimal_time/float_time:.1f}倍の時間")
# 結果例:
# float処理時間: 0.0089秒
# decimal処理時間: 0.1234秒
# decimal は float の約13.9倍の時間
メモリ使用量の違い:
import sys
float_value = 3.14159
decimal_value = Decimal('3.14159')
print(f"float のメモリ使用量: {sys.getsizeof(float_value)} bytes")
print(f"decimal のメモリ使用量: {sys.getsizeof(decimal_value)} bytes")
# 結果例:
# float のメモリ使用量: 24 bytes
# decimal のメモリ使用量: 104 bytes
用途別の使い分けガイドライン
floatを使うべき場面:
科学計算:
import math
# 物理計算の例
velocity = 30.0 # m/s
time = 2.5 # s
distance = velocity * time # 近似で十分
# 統計計算の例
data = [1.2, 2.3, 3.4, 4.5, 5.6]
average = sum(data) / len(data) # 近似で十分
グラフィック・ゲーム:
# ゲームの座標計算
player_x = 100.5
player_y = 200.3
speed = 5.0
# 移動計算(完全な精度は不要)
new_x = player_x + speed
new_y = player_y + speed
機械学習:
import numpy as np
# ニューラルネットワークの重み
weights = np.array([0.1, 0.2, 0.3, 0.4]) # 近似で十分
bias = 0.5
# 計算(完全な精度は不要)
output = np.dot(weights, [1, 1, 1, 1]) + bias
decimalを使うべき場面:
金融計算:
from decimal import Decimal
# 銀行の利息計算
principal = Decimal('1000000') # 元金100万円
rate = Decimal('0.02') # 年利2%
years = Decimal('5') # 5年
# 複利計算(正確性が必要)
amount = principal * (Decimal('1') + rate) ** years
print(f"5年後の金額: {amount:.2f}円")
会計システム:
# 請求書の計算
items = [
{'name': '商品A', 'price': Decimal('1980'), 'qty': 3},
{'name': '商品B', 'price': Decimal('2480'), 'qty': 2},
{'name': '商品C', 'price': Decimal('980'), 'qty': 1},
]
total = Decimal('0')
for item in items:
subtotal = item['price'] * item['qty']
total += subtotal
print(f"{item['name']}: {item['price']}円 × {item['qty']}個 = {subtotal}円")
tax = total * Decimal('0.1') # 消費税
grand_total = total + tax
print(f"小計: {total}円")
print(f"消費税: {tax}円")
print(f"総計: {grand_total}円")
測定・計量システム:
# 薬品の調合
ingredient_a = Decimal('15.25') # g
ingredient_b = Decimal('8.75') # g
ingredient_c = Decimal('2.50') # g
total_weight = ingredient_a + ingredient_b + ingredient_c
print(f"総重量: {total_weight}g") # 正確性が重要
変換時の注意点
安全な変換方法:
文字列経由での変換:
# float → decimal (推奨方法)
float_value = 123.45
decimal_value = Decimal(str(float_value)) # 文字列経由
print(decimal_value) # 123.45
# 直接変換は避ける
decimal_bad = Decimal(float_value) # 浮動小数点誤差が含まれる
print(decimal_bad) # 123.45000000000000284...
型チェックと条件分岐:
def safe_add(a, b):
"""安全な加算関数"""
# 両方をDecimalに統一
if not isinstance(a, Decimal):
a = Decimal(str(a))
if not isinstance(b, Decimal):
b = Decimal(str(b))
return a + b
# 使用例
result1 = safe_add(Decimal('100'), Decimal('200')) # 300
result2 = safe_add(100, 200) # 300
result3 = safe_add(100.5, Decimal('200.25')) # 300.75
このように、用途に応じて適切に使い分けることが重要です。
実際の活用例

ECサイトの価格計算システム
商品価格計算クラス:
from decimal import Decimal, ROUND_HALF_UP
class PriceCalculator:
"""ECサイト用価格計算クラス"""
def __init__(self, tax_rate='0.10'):
self.tax_rate = Decimal(tax_rate)
def calculate_subtotal(self, items):
"""小計を計算"""
subtotal = Decimal('0')
for item in items:
price = Decimal(str(item['price']))
quantity = Decimal(str(item['quantity']))
subtotal += price * quantity
return subtotal
def calculate_tax(self, subtotal):
"""消費税を計算(1円未満四捨五入)"""
tax = subtotal * self.tax_rate
return tax.quantize(Decimal('1'), rounding=ROUND_HALF_UP)
def apply_discount(self, amount, discount_rate):
"""割引を適用"""
discount_rate = Decimal(str(discount_rate))
discount = amount * discount_rate
return amount - discount
def calculate_total(self, items, discount_rate=0):
"""総額を計算"""
subtotal = self.calculate_subtotal(items)
# 割引適用
if discount_rate > 0:
subtotal = self.apply_discount(subtotal, discount_rate)
# 消費税計算
tax = self.calculate_tax(subtotal)
total = subtotal + tax
return {
'subtotal': subtotal,
'tax': tax,
'total': total
}
# 使用例
calculator = PriceCalculator()
# 購入商品
cart_items = [
{'name': 'Tシャツ', 'price': 2980, 'quantity': 2},
{'name': 'ジーンズ', 'price': 7980, 'quantity': 1},
{'name': '靴下', 'price': 580, 'quantity': 3},
]
# 10%割引クーポン適用
result = calculator.calculate_total(cart_items, discount_rate=0.1)
print("=== 購入明細 ===")
for item in cart_items:
item_total = Decimal(str(item['price'])) * Decimal(str(item['quantity']))
print(f"{item['name']}: {item['price']}円 × {item['quantity']}個 = {item_total}円")
print(f"\n小計: {result['subtotal']}円")
print(f"消費税: {result['tax']}円")
print(f"総計: {result['total']}円")
給与計算システム
給与計算の実装例:
from decimal import Decimal, ROUND_DOWN
class SalaryCalculator:
"""給与計算クラス"""
def __init__(self):
# 税率・保険料率(2024年基準)
self.income_tax_rate = Decimal('0.05') # 所得税(簡素化)
self.resident_tax_rate = Decimal('0.10') # 住民税(簡素化)
self.health_insurance_rate = Decimal('0.05') # 健康保険
self.pension_rate = Decimal('0.0915') # 厚生年金
self.employment_insurance_rate = Decimal('0.003') # 雇用保険
def calculate_social_insurance(self, base_salary):
"""社会保険料を計算"""
health_insurance = (base_salary * self.health_insurance_rate).quantize(
Decimal('1'), rounding=ROUND_DOWN)
pension = (base_salary * self.pension_rate).quantize(
Decimal('1'), rounding=ROUND_DOWN)
employment_insurance = (base_salary * self.employment_insurance_rate).quantize(
Decimal('1'), rounding=ROUND_DOWN)
return {
'health_insurance': health_insurance,
'pension': pension,
'employment_insurance': employment_insurance,
'total': health_insurance + pension + employment_insurance
}
def calculate_tax(self, taxable_income):
"""税金を計算"""
income_tax = (taxable_income * self.income_tax_rate).quantize(
Decimal('1'), rounding=ROUND_DOWN)
resident_tax = (taxable_income * self.resident_tax_rate).quantize(
Decimal('1'), rounding=ROUND_DOWN)
return {
'income_tax': income_tax,
'resident_tax': resident_tax,
'total': income_tax + resident_tax
}
def calculate_net_salary(self, base_salary):
"""手取り給与を計算"""
base_salary = Decimal(str(base_salary))
# 社会保険料計算
social_insurance = self.calculate_social_insurance(base_salary)
# 課税所得(基本給 - 社会保険料)
taxable_income = base_salary - social_insurance['total']
# 税金計算
tax = self.calculate_tax(taxable_income)
# 手取り額
net_salary = base_salary - social_insurance['total'] - tax['total']
return {
'base_salary': base_salary,
'social_insurance': social_insurance,
'tax': tax,
'net_salary': net_salary
}
# 使用例
calculator = SalaryCalculator()
monthly_salary = 300000 # 月給30万円
result = calculator.calculate_net_salary(monthly_salary)
print("=== 給与明細 ===")
print(f"基本給: {result['base_salary']:,}円")
print("\n【控除項目】")
print(f"健康保険料: {result['social_insurance']['health_insurance']:,}円")
print(f"厚生年金保険料: {result['social_insurance']['pension']:,}円")
print(f"雇用保険料: {result['social_insurance']['employment_insurance']:,}円")
print(f"所得税: {result['tax']['income_tax']:,}円")
print(f"住民税: {result['tax']['resident_tax']:,}円")
print(f"\n控除合計: {result['social_insurance']['total'] + result['tax']['total']:,}円")
print(f"手取り額: {result['net_salary']:,}円")
投資収益計算システム
複利計算の実装:
from decimal import Decimal, getcontext
class InvestmentCalculator:
"""投資計算クラス"""
def __init__(self, precision=10):
# 計算精度を設定
getcontext().prec = precision
def compound_interest(self, principal, annual_rate, years, compound_frequency=1):
"""複利計算
Args:
principal: 元本
annual_rate: 年利率(小数)
years: 投資期間(年)
compound_frequency: 年間複利回数(1=年1回、12=月1回)
"""
principal = Decimal(str(principal))
annual_rate = Decimal(str(annual_rate))
years = Decimal(str(years))
compound_frequency = Decimal(str(compound_frequency))
# 複利計算式: A = P(1 + r/n)^(nt)
rate_per_period = annual_rate / compound_frequency
total_periods = compound_frequency * years
# (1 + r/n)^(nt) を計算
growth_factor = (Decimal('1') + rate_per_period) ** total_periods
final_amount = principal * growth_factor
return {
'principal': principal,
'final_amount': final_amount,
'total_return': final_amount - principal,
'return_rate': ((final_amount / principal) - Decimal('1')) * Decimal('100')
}
def monthly_investment(self, monthly_amount, annual_rate, years):
"""積立投資計算"""
monthly_amount = Decimal(str(monthly_amount))
monthly_rate = Decimal(str(annual_rate)) / Decimal('12')
total_months = Decimal(str(years)) * Decimal('12')
# 等比級数の和の公式を使用
if monthly_rate == 0:
final_amount = monthly_amount * total_months
else:
# FV = PMT × (((1 + r)^n - 1) / r)
growth_factor = (Decimal('1') + monthly_rate) ** total_months
final_amount = monthly_amount * ((growth_factor - Decimal('1')) / monthly_rate)
total_invested = monthly_amount * total_months
return {
'monthly_amount': monthly_amount,
'total_invested': total_invested,
'final_amount': final_amount,
'total_return': final_amount - total_invested,
'years': years
}
# 使用例
calc = InvestmentCalculator()
# 1. 一括投資の例
lump_sum = calc.compound_interest(
principal=1000000, # 100万円
annual_rate=0.05, # 年利5%
years=10, # 10年間
compound_frequency=12 # 月複利
)
print("=== 一括投資シミュレーション ===")
print(f"元本: {lump_sum['principal']:,}円")
print(f"最終金額: {lump_sum['final_amount']:.0f}円")
print(f"利益: {lump_sum['total_return']:.0f}円")
print(f"収益率: {lump_sum['return_rate']:.2f}%")
# 2. 積立投資の例
monthly_invest = calc.monthly_investment(
monthly_amount=50000, # 月5万円
annual_rate=0.05, # 年利5%
years=20 # 20年間
)
print("\n=== 積立投資シミュレーション ===")
print(f"月額投資: {monthly_invest['monthly_amount']:,}円")
print(f"投資期間: {monthly_invest['years']}年")
print(f"投資総額: {monthly_invest['total_invested']:,}円")
print(f"最終金額: {monthly_invest['final_amount']:.0f}円")
print(f"利益: {monthly_invest['total_return']:.0f}円")
在庫管理システム
在庫コスト計算:
from decimal import Decimal, ROUND_HALF_UP
from datetime import datetime, timedelta
class InventoryManager:
"""在庫管理クラス"""
def __init__(self):
self.inventory = {}
def add_stock(self, product_id, quantity, unit_cost, date=None):
"""在庫追加(移動平均法)"""
if date is None:
date = datetime.now()
quantity = Decimal(str(quantity))
unit_cost = Decimal(str(unit_cost))
if product_id not in self.inventory:
self.inventory[product_id] = {
'quantity': Decimal('0'),
'total_cost': Decimal('0'),
'average_cost': Decimal('0'),
'transactions': []
}
current = self.inventory[product_id]
# 新しい在庫の追加
new_total_quantity = current['quantity'] + quantity
new_total_cost = current['total_cost'] + (quantity * unit_cost)
# 移動平均単価の計算
if new_total_quantity > 0:
new_average_cost = new_total_cost / new_total_quantity
else:
new_average_cost = Decimal('0')
# 在庫情報更新
current['quantity'] = new_total_quantity
current['total_cost'] = new_total_cost
current['average_cost'] = new_average_cost.quantize(
Decimal('0.01'), rounding=ROUND_HALF_UP)
# 取引履歴追加
current['transactions'].append({
'date': date,
'type': 'IN',
'quantity': quantity,
'unit_cost': unit_cost,
'balance': new_total_quantity
})
return current['average_cost']
def remove_stock(self, product_id, quantity, date=None):
"""在庫出庫"""
if date is None:
date = datetime.now()
quantity = Decimal(str(quantity))
if product_id not in self.inventory:
raise ValueError(f"商品 {product_id} が見つかりません")
current = self.inventory[product_id]
if current['quantity'] < quantity:
raise ValueError(f"在庫不足: 現在庫{current['quantity']}, 出庫要求{quantity}")
# 出庫コスト(移動平均単価 × 出庫数量)
outbound_cost = current['average_cost'] * quantity
# 在庫更新
current['quantity'] -= quantity
current['total_cost'] -= outbound_cost
# 取引履歴追加
current['transactions'].append({
'date': date,
'type': 'OUT',
'quantity': quantity,
'unit_cost': current['average_cost'],
'balance': current['quantity']
})
return outbound_cost
def get_inventory_value(self, product_id=None):
"""在庫評価額を取得"""
if product_id:
if product_id in self.inventory:
item = self.inventory[product_id]
return item['quantity'] * item['average_cost']
return Decimal('0')
else:
total_value = Decimal('0')
for item in self.inventory.values():
total_value += item['quantity'] * item['average_cost']
return total_value
# 使用例
inventory = InventoryManager()
# 商品A の在庫取引
print("=== 商品A 在庫管理 ===")
# 入庫1: 100個 @ 1,000円
avg_cost1 = inventory.add_stock('A001', 100, 1000)
print(f"入庫1: 100個 @ 1,000円 → 平均単価: {avg_cost1}円")
# 入庫2: 200個 @ 1,200円
avg_cost2 = inventory.add_stock('A001', 200, 1200)
print(f"入庫2: 200個 @ 1,200円 → 平均単価: {avg_cost2}円")
# 出庫: 150個
outbound_cost = inventory.remove_stock('A001', 150)
print(f"出庫: 150個 → 出庫コスト: {outbound_cost}円")
# 現在の在庫状況
current_inventory = inventory.inventory['A001']
inventory_value = inventory.get_inventory_value('A001')
print(f"\n現在庫: {current_inventory['quantity']}個")
print(f"平均単価: {current_inventory['average_cost']}円")
print(f"在庫評価額: {inventory_value}円")
よくある間違いとベストプラクティス
よくある間違い
間違い1:floatからの直接変換
悪い例:
# ❌ 浮動小数点誤差が持ち込まれる
price = 19.99
decimal_price = Decimal(price)
print(decimal_price)
# 19.9899999999999984456877655247808434069156646728515625
正しい例:
# ✅ 文字列経由で正確に変換
price = 19.99
decimal_price = Decimal(str(price))
print(decimal_price)
# 19.99
間違い2:丸め処理を忘れる
悪い例:
# ❌ 丸め処理なしだと予期しない精度
price = Decimal('100')
tax_rate = Decimal('0.08')
tax = price * tax_rate / Decimal('3') # 3分割
print(tax) # 2.666666666666666666666666667
正しい例:
# ✅ 適切な丸め処理
price = Decimal('100')
tax_rate = Decimal('0.08')
tax = price * tax_rate / Decimal('3')
tax_rounded = tax.quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
print(tax_rounded) # 2.67
間違い3:型の混在
悪い例:
# ❌ DecimalとFloatの混在
decimal_price = Decimal('100.50')
float_tax = 0.1
total = decimal_price + float_tax # 型が混在
print(type(total)) # <class 'decimal.Decimal'> だが精度が落ちる
正しい例:
# ✅ 型を統一
decimal_price = Decimal('100.50')
decimal_tax = Decimal('0.1')
total = decimal_price + decimal_tax
print(total) # 100.6
ベストプラクティス
1. 一貫した型の使用
from decimal import Decimal
class MoneyCalculator:
"""金額計算用クラス"""
@staticmethod
def to_decimal(value):
"""安全にDecimalに変換"""
if isinstance(value, Decimal):
return value
return Decimal(str(value))
def add(self, a, b):
"""安全な加算"""
return self.to_decimal(a) + self.to_decimal(b)
def multiply(self, a, b):
"""安全な乗算"""
return self.to_decimal(a) * self.to_decimal(b)
# 使用例
calc = MoneyCalculator()
result = calc.add(100.5, Decimal('200.25')) # 型を気にしなくて良い
2. 設定の管理
from decimal import getcontext, localcontext, ROUND_HALF_UP
from contextlib import contextmanager
@contextmanager
def money_context():
"""金額計算用のコンテキスト"""
with localcontext() as ctx:
ctx.prec = 28 # 高精度
ctx.rounding = ROUND_HALF_UP # 四捨五入
yield ctx
# 使用例
with money_context():
result = Decimal('100') / Decimal('3')
rounded = result.quantize(Decimal('0.01'))
print(rounded) # 33.33
3. エラーハンドリング
from decimal import Decimal, InvalidOperation
def safe_decimal_calculation(value1, value2, operation):
"""安全なDecimal計算"""
try:
d1 = Decimal(str(value1))
d2 = Decimal(str(value2))
if operation == 'add':
return d1 + d2
elif operation == 'subtract':
return d1 - d2
elif operation == 'multiply':
return d1 * d2
elif operation == 'divide':
if d2 == 0:
raise ValueError("0で除算はできません")
return d1 / d2
else:
raise ValueError(f"未対応の操作: {operation}")
except InvalidOperation as e:
raise ValueError(f"無効な数値: {e}")
except Exception as e:
raise ValueError(f"計算エラー: {e}")
# 使用例
try:
result = safe_decimal_calculation("100.5", "0.1", "multiply")
print(result) # 10.05
except ValueError as e:
print(f"エラー: {e}")
4. 設定のカプセル化
class PrecisionCalculator:
"""精度管理付き計算クラス"""
def __init__(self, precision=10, rounding=ROUND_HALF_UP):
self.precision = precision
self.rounding = rounding
def calculate(self, func, *args, **kwargs):
"""指定された精度で計算を実行"""
with localcontext() as ctx:
ctx.prec = self.precision
ctx.rounding = self.rounding
return func(*args, **kwargs)
def format_currency(self, amount, currency_symbol='¥'):
"""通貨形式でフォーマット"""
formatted = f"{currency_symbol}{amount:,.2f}"
return formatted
# 使用例
calc = PrecisionCalculator(precision=20)
def complex_calculation():
return Decimal('1') / Decimal('3') * Decimal('9999.99')
result = calc.calculate(complex_calculation)
formatted = calc.format_currency(result)
print(formatted) # ¥3,333.33
まとめ:decimalモジュールで正確な計算を
重要なポイントをおさらい
decimalモジュールの基本
- 10進数での正確な計算:浮動小数点誤差を完全に回避
- 文字列初期化:
Decimal('0.1')
で正確な値を設定 - カスタマイズ可能:精度と丸め方法を用途に応じて調整
実装のベストプラクティス
- 型の統一:計算全体でDecimal型を維持
- エラーハンドリング:適切な例外処理
- 設定管理:contextを使った一時的な設定変更
コメント