国税庁の例題と合計が合ったのに、消費税の計算は間違っていた。端数処理の試験を作り直す

国税庁の例題と合計が合ったのに、消費税の計算は間違っていた。端数処理の試験を作り直す

round(1134.5) が 1134 を返しました。四捨五入なら 1135 のはずです。

このずれに気づいたのは、デジタル商品の請求書を想定して消費税を計算する小さなスクリプトを書いていたときです。ただ、本題はこのずれではありません。本題は、そのスクリプトが国税庁の例題と同じ合計を出したのに、計算方法としては間違っていたことです。

先に範囲を書いておきます。ここで使った注文は、すべて私が作った合成のカートです。実際の売上、返品、値引き、販売プラットフォームが発行する書類は扱っていません。登録するかどうかの判断材料でもありません。確かめたのは、AI に計算コードを書かせるときに渡す「試験の数字」が、どこまで間違いを止められるかです。

10月5日の記事では、980円の商品に対する決済手数料を見積もりました(前回の記事)。手数料の次は、請求書に書く消費税の1円単位が気になりました。

国税庁の規則は一行で足りる

適格請求書(インボイス)に書く消費税額で1円未満の端数が出るときは、1つの請求書につき、税率ごとに1回だけ端数処理をします。No.6371と問57にそう書いてあります(根拠は消費税法の施行令70条の10)。切上げ、切捨て、四捨五入のどれを使うかは任意です。

もう1つ大事な注記があります。個々の商品ごとに税額を計算して端数を処理し、その合計を税額として書くことは認められません。

例題はQ&A問57にあります。税込60,000円と40,000円は本文の例です。税抜11,345円と12,549円は問57の参考例です。9,987円と12,345円も同じです。見出しは「(参考)1枚の書類に複数の適格請求書を合わせて記載する場合」です。自社分と他社分を1枚に書く場面の数字で、媒介者交付や代理交付、立替金精算書が該当します。この記事では1社の請求書に商品が2行並ぶ場合の見立てとして、そのまま使います。

  • 税込60,000円の10%対象は、消費税5,454円
  • 税込40,000円の8%対象は、消費税2,962円
  • 10%対象で税抜11,345円と12,549円の2行は、合計の消費税が2,389円
  • 8%対象で税抜9,987円と12,345円の2行は、合計の消費税が1,786円

この4つの数字を試験にしました。

最初の版は、2,389円を出した

手癖で書いた最初の版が次です。float で掛けて、Python の round() で丸めて、行ごとに足します。

# v1_float_round.py
lines = [11345, 12549]  # 税抜の対価の額(国税庁Q&A問57の例)

def tax_line(price):
    return round(price * 0.10)

per_line = sum(tax_line(p) for p in lines)
print("行ごと:", [tax_line(p) for p in lines], "合計", per_line)
print("float確認:", 11345 * 0.10, 12549 * 0.10)
print("round(1134.5) =", round(1134.5), "/ round(1135.5) =", round(1135.5))
python3 v1_float_round.py
行ごと: [1134, 1255] 合計 2389
float確認: 1134.5 1254.9
round(1134.5) = 1134 / round(1135.5) = 1136

合計は 2389 で、国税庁の 2,389円と同じです。試験としては合格でした。

でも、中身を見ると合っている理由がありません。1行目の 1134 は、round() が .5 を偶数側へ寄せる動きの結果です(Python の公式説明では「同じ距離なら偶数の側」)。2行目の 1255 は四捨五入です。国税庁の例の行ごとの税額は 1,134円と 1,254円ですから、2行目は1円違います。片方は偶数丸め、もう片方は四捨五入という別々の癖が、たまたま打ち消し合って合計だけ一致しました。

この2件では float の誤差は出ませんでした。ただ、金額の計算に float を残す理由はありません。

書き直した版と、4つの数字

2つ目の版では、Decimal を使い、丸め方を切捨てに固定しました。行ごとに丸める関数と、請求書単位で1回だけ丸める関数、税込合計から逆算する関数の3つを並べています。

invoice_tax.py の全文
# invoice_tax.py
from decimal import Decimal, ROUND_FLOOR, ROUND_HALF_UP

def to_yen(x, mode=ROUND_FLOOR):
    return int(Decimal(x).quantize(Decimal("1"), rounding=mode))

def tax_per_line(lines, rate_pct=10, mode=ROUND_FLOOR):
    """行ごとに丸めて足す(適格請求書では認められない)"""
    return sum(to_yen(Decimal(p) * rate_pct / 100, mode) for p in lines)

def tax_per_invoice(lines, rate_pct=10, mode=ROUND_FLOOR):
    """税率ごとに合計してから1回だけ丸める"""
    return to_yen(Decimal(sum(lines)) * rate_pct / 100, mode)

def tax_from_inclusive(total, rate_pct=10, mode=ROUND_FLOOR):
    """税込合計から逆算して1回だけ丸める"""
    return to_yen(Decimal(total) * rate_pct / (100 + rate_pct), mode)

国税庁の4つの数字を突き合わせる試験は、次のとおりです。

# check_nta.py
from invoice_tax import *

cases = [
    ("10%対象 税込60,000円", tax_from_inclusive(60000, 10), 5454),
    ("8%対象 税込40,000円", tax_from_inclusive(40000, 8), 2962),
    ("10%対象 税抜 11,345+12,549", tax_per_invoice([11345, 12549]), 2389),
    ("8%対象 税抜 9,987+12,345", tax_per_invoice([9987, 12345], 8), 1786),
]
for name, got, want in cases:
    print(f"{'OK' if got == want else 'NG'}  {name}: 計算={got} 国税庁={want}")

print("行ごとに切捨て(10%):", tax_per_line([11345, 12549]))
print("行ごとに切捨て(8%) :", tax_per_line([9987, 12345], 8))
python3 check_nta.py
OK  10%対象 税込60,000円: 計算=5454 国税庁=5454
OK  8%対象 税込40,000円: 計算=2962 国税庁=2962
OK  10%対象 税抜 11,345+12,549: 計算=2389 国税庁=2389
OK  8%対象 税抜 9,987+12,345: 計算=1786 国税庁=1786
行ごとに切捨て(10%): 2388
行ごとに切捨て(8%) : 1785

下の2行に注目してください。同じ切捨てでも、1社の適格請求書で商品ごとに丸めると2,388円と1,785円になり、請求書単位の答えより1円少なくなります。この記事では、1社の請求書で行ごとの丸めを落とす試験に使います。代理交付のように自社分と他社分を税率ごとに別々に丸める場合は、分けて丸めるのが正しいやり方です。逆に、最初の版のように丸め方が混ざった実装は、この例題だけでは落とせませんでした。

差が出ない見本を作っていた

次に、行ごとの丸めと請求書単位の丸めが、どのくらいの割合で食い違うかを調べました。商品を2〜5点選ぶカートを1万件作り、両方の方法で税額を出して比べます。

最初の価格帯は500円から3,980円まで7種類です。デジタル商品でよく見かける価格を並べました。これを税抜価格として、行ごとの合計と請求書単位の税額を比べます。

# simulate.py
import random
from invoice_tax import *

random.seed(20261009)
prices = [500, 980, 1280, 1480, 1980, 2480, 3980]  # 税抜の価格帯
N = 10000
diff = 0
worst = 0
first = None
for _ in range(N):
    cart = [random.choice(prices) for _ in range(random.randint(2, 5))]
    a, b = tax_per_line(cart), tax_per_invoice(cart)
    if a != b:
        diff += 1
        worst = max(worst, abs(a - b))
        first = first or (cart, a, b)
print(f"カート{N}件のうち、行ごと合計と請求書単位が食い違った件数: {diff}")
print("最大の差(円):", worst)
print("最初の食い違い:", first)
python3 simulate.py
カート10000件のうち、行ごと合計と請求書単位が食い違った件数: 0
最大の差(円): 0
最初の食い違い: None

0件でした。少し考えると当たり前です。税抜価格がすべて10の倍数なら、10%の税額は必ず整数になります。端数が出ないので、どう丸めても同じです。

試験の見本が、問題の起きない価格だけで組まれていました。これも失敗です。国税庁の例題が11,345円や12,549円のように端数の出る数字を使っている意味を、ここで実感しました。

価格の置き方を変えて、4通りで比べる

価格を変えて、1万件を比べ直しました。乱数の種は固定しています。

simulate2.py の全文
# simulate2.py
import random
from invoice_tax import *

def run(label, prices, calc_line, calc_invoice, n=10000):
    random.seed(20261009)
    diff, worst, first = 0, 0, None
    for _ in range(n):
        cart = [random.choice(prices) for _ in range(random.randint(2, 5))]
        a, b = calc_line(cart), calc_invoice(cart)
        if a != b:
            diff += 1
            worst = max(worst, abs(a - b))
            first = first or (cart, a, b)
    print(f"{label}: {diff}/{n}件が食い違い, 最大{worst}円差, 例={first}")

# 税抜価格(末尾0だけ)
run("税抜 500..3980(末尾0)", [500, 980, 1280, 1480, 1980, 2480, 3980],
    tax_per_line, tax_per_invoice)
# 税抜価格(末尾が0でない)
run("税抜 1234/1490/1999", [1234, 1490, 1999],
    tax_per_line, tax_per_invoice)
# 税込価格を逆算
run("税込 500..3980", [500, 980, 1280, 1480, 1980, 2480, 3980],
    lambda c: sum(tax_from_inclusive(p) for p in c),
    lambda c: tax_from_inclusive(sum(c)))
# 税込価格の1行ずつを四捨五入で逆算した場合
run("税込 四捨五入", [500, 980, 1280, 1480, 1980, 2480, 3980],
    lambda c: sum(tax_from_inclusive(p, 10, ROUND_HALF_UP) for p in c),
    lambda c: tax_from_inclusive(sum(c), 10, ROUND_HALF_UP))
python3 simulate2.py
税抜 500..3980(末尾0): 0/10000件が食い違い, 最大0円差, 例=None
税抜 1234/1490/1999: 6754/10000件が食い違い, 最大4円差, 例=([1234, 1999], 322, 323)
税込 500..3980: 7011/10000件が食い違い, 最大3円差, 例=([3980, 1480, 980], 584, 585)
税込 四捨五入: 4850/10000件が食い違い, 最大2円差, 例=([3980, 1480, 980], 586, 585)

整理すると、次の表になります。

価格の置き方食い違い(1万件中)最大の差
税抜、末尾が00件0円
税抜、1,234円など6,754件4円
税込を切捨てで逆算7,011件3円
税込を四捨五入で逆算4,850件2円

食い違いの件数は、私が選んだ価格帯と、2〜5点という買い方の仮定で決まる数字です。実際の注文で同じ割合になるとは限りません。見てほしいのは、価格の置き方しだいで食い違いが0件から7割まで動く点です。

1,234円の商品を3本買うカートで確かめると、行ごとの合計は 369円、請求書単位では 370円でした。税込の例では、3,980円、1,480円、980円の3点で、行ごとが 584円、請求書単位が 585円です。

私が980円や1,480円のような税込価格で売るなら、この食い違いは日常的に起きます。末尾0の見本だけでは、ここに気づけませんでした。

試験に入れる数字

AI にコードを頼むときの試験は、今回の結果から次のように変えます。

  • 国税庁Q&A問57の4つの数字を、そのまま入れる。
  • 1社の適格請求書で商品ごとに丸めたときの値(2,388円、1,785円)を「不合格になるべき答え」として入れる。代理交付で自社分と他社分を分けて丸める場合は、この限りではありません。
  • 価格が10の倍数だけの見本では、端数が出ないので、試験に数えない。
  • 1,234円×3のような、行ごとと請求書単位で答えが分かれるカートを最低1つ入れる。
  • 金額の型は Decimal か整数にして、round() は使わない。

国税庁の例題が通っても、実装が正しいとは限りません。最初の版がそうでした。例題に加えて、答えが分かれるカートを足してはじめて、行ごとの丸めを落とせます。

まだ決めていないこともあります。税込価格で売るか税抜価格で売るかは、この検証では選んでいません。値引きや返品が入った請求書でどう丸めるかも、今回は調べていません。

出典

この記事をシェア