CHAPTER 1 ・ 書き方を一段上げる / Lesson 04
お金の計算を正確にする(端数処理)
0.1 + 0.2 が合わない理由。Decimal で、消費税や按分の端数を思いどおりに丸めます。
このレッスンが終わったら
「小数の足し算は誤差が出ることがある。お金は Decimal で持ち、切り捨て・四捨五入を自分で指定する」と言える。
こんな場面、ありませんか?
請求書の金額を計算したら、1円だけ合わない。経理では、よくある悩みです。Pythonの小数の計算では、次のようなことが起きます。
▶ 実行を押すと、ここに結果が出ます
0.30000000000000004 と表示され、0.3 との比較は False です。Pythonの間違いではありません。コンピュータは数を2進数で持つため、0.1 のような数は、ぴったりは表せず、ごくわずかな誤差が出るのです。
もうひとつ、四捨五入にも落とし穴があります。Pythonの round() は、基礎編で使った人も多いはずです。
▶ 実行を押すと、ここに結果が出ます
学校で習った四捨五入なら 1 2 3 4 になるはずが、0 2 2 4 です。round() は、ちょうど半分のとき偶数のほうへ寄せる銀行丸めという方式だからです。税の計算は、「切り捨て」「切り上げ」「四捨五入」のどれにするかを、自分で決めて指定する必要があります。そのための道具が、Decimalです。
Decimal で計算してみる
Decimal は、10進数のまま計算する型です。文字から作るのが、鉄則です。
▶ 実行を押すと、ここに結果が出ます
0.3 で、比較も True になりました。最後の行のように、0.1(小数)から作ると、誤差まで取り込んでしまいます。Decimal("0.1") のように、引用符で囲んだ文字から作りましょう。
次は、端数の丸めです。1,285円の10%(128.5円)を、整数の円にします。
▶ 実行を押すと、ここに結果が出ます
同じ 128.5 から、128(切り捨て)、129(四捨五入)、129(切り上げ)と、指定どおりの結果が出ました。
丸めの指定を読む
quantize は「桁をそろえる」という意味で、丸めに使います。
tax丸めたい数(Decimal).quantize(桁をそろえる道具Decimal("1")1の位までにするrounding=丸め方の指定ROUND_DOWN切り捨て日本語にすると
ROUND_DOWN切り捨て(0に近いほうへ) 128.5 は 128ROUND_HALF_UP四捨五入(半分以上は切り上げ) 128.5 は 129ROUND_UP切り上げ(0から遠いほうへ) 128.1 も 129Decimal("1") / Decimal("0.1")1の位まで、小数1桁まで、にそろえる指定(quantize に渡す)float(誤差が出る)
0.1 + 0.2
Decimal(文字から作る)
Decimal("0.1") + Decimal("0.2")消費税と按分を計算してみる
消費税には、標準税率10%と軽減税率8%があります。端数の処理(切り捨て・切り上げ・四捨五入)は、事業者が選べます。まず、税額を求める関数を作り、明細ごとに丸める場合と、請求書1枚の合計に1回だけ丸める場合を、比べます。
▶ 実行を押すと、ここに結果が出ます
明細ごとに切り捨てると 572 円、合計に1回だと 573 円で、1円違います。どちらが正しいのでなく、ルールの違いです。インボイス制度では、税額の端数処理は、税率ごとに、請求書1枚につき1回とされています。会社の請求書の作り方は、社内のルールや税理士に確認しましょう。
税込の金額から、税額を逆算する。 税込1,080円(8%)の税額は、税込 × 8 ÷ 108 で求めます。
▶ 実行を押すと、ここに結果が出ます
実際のデータで、税率ごとにまとめる。 sales_detail.csv の先頭5行を1枚の請求書とみなし、products.csv の税率ごとに税抜金額を合計して、税額を1回ずつ求めます。
この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。
▶ 実行を押すと、ここに結果が出ます
按分(あんぶん)。 10,000円を3人で分けると、3,333円ずつでは1円足りません。端数の置き場所を決めます。ここでは、割り切れない分を、先頭の人から1円ずつ足します。
▶ 実行を押すと、ここに結果が出ます
divmod(10000, 3) は、「割り算の商と余り」を一度に返します。商(3333)が基本の額で、余り(1)の人数ぶん、1円ずつ多くしています。
円を整数で持つ。 円は、整数で持てば誤差は出ません。切り捨てなら //、四捨五入なら「半分(50)を足してから //」で、Decimalなしでも求まります。
▶ 実行を押すと、ここに結果が出ます
ただし、/(普通の割り算)は小数になって誤差が戻ってきます。割り算を含む計算や、税率が小数のときは、Decimal が安心です。
自分で書く
お題① 税抜 1285 円の、軽減税率8%の消費税を、切り捨てで求めて表示してください。
▶ 実行を押すと、ここに結果が出ます
ヒントを見る
tax = price * Decimal("0.08") のあと、tax.quantize(Decimal("1"), rounding=ROUND_DOWN) を print します。1285 × 0.08 = 102.8 なので、切り捨てると 102 です。
答えの例を見る
from decimal import Decimal, ROUND_DOWN
price = Decimal("1285")
tax = price * Decimal("0.08")
print(tax.quantize(Decimal("1"), rounding=ROUND_DOWN))お題② 請求書1枚の明細が、税抜 2475 円、1865 円、3295 円です。10%の消費税を、合計に対して1回だけ切り捨てで求めて表示してください。
▶ 実行を押すと、ここに結果が出ます
ヒントを見る
sum(lines) は 7635 です。Decimal(sum(lines)) * Decimal("0.1") を quantize で切り捨てると、763.5 から 763 になります。
答えの例を見る
from decimal import Decimal, ROUND_DOWN
lines = [2475, 1865, 3295]
tax = Decimal(sum(lines)) * Decimal("0.1")
print(tax.quantize(Decimal("1"), rounding=ROUND_DOWN))お題③ 家賃100,000円を、3つの部署に、人数の比 3:2:2 で按分します。最後の部署で端数を調整して、金額のリストを表示してください(合計が100,000円になること)。
▶ 実行を押すと、ここに結果が出ます
ヒントを見る
parts = [total * r // sum(ratios) for r in ratios] で、切り捨てた額が出ます。そのあと parts[-1] = total - sum(parts[:-1]) で、最後の部署に差額を寄せます。
答えの例を見る
total = 100000 ratios = [3, 2, 2] parts = [total * r // sum(ratios) for r in ratios] parts[-1] = total - sum(parts[:-1]) print(parts)
仕事ならどう使う?
理解チェック金額の計算で、0.1 を Decimal にする書き方は、どれでしょう?
理解チェック10,000円を3人で割り、3,333円ずつ配ると、合計はいくらになるでしょう。合計を10,000円に合わせるには、どうしますか?