消費税の端数処理は「税率ごとに1回」

請求書システムで最も多い実装ミスです。行ごとに丸めても、合計してから丸めても、 どちらも1円単位で違う額になります。制度が求めるのは税率ごとに1回だけです。

何が問題になるのか

税抜105円の商品を3行、税率10%、切捨てで計算してみます。

計算方法消費税額制度上の可否
行ごとに 105 × 10% = 10.5 → 10 円、それを3行合計30 円認められない
税率ごとに合計 315 円 × 10% = 31.5 → 31 円31 円正しい

差は1円ですが、明細が増えるほど開きます。しかも買手の仕入税額控除の額に影響するため、 «自社の帳簿では合っているのに取引先と1円合わない» という形で表面化します。

根拠(国税庁Q&A 問57)

適格請求書の記載事項である消費税額等に1円未満の端数が生じる場合は、一の適格請求書につき、 税率ごとに1回の端数処理を行う必要があります(消令70の10、基通1−8−15)。
なお、切上げ、切捨て、四捨五入などの端数処理の方法については、任意の方法とすることができます。
(注)一の適格請求書に記載されている個々の商品ごとに消費税額等を計算し、1円未満の端数処理を行い、 その合計額を消費税額等として記載することは認められません。

実装するときの3つのルール

1. 丸めの単位は「税率グループ」

10%対象と8%対象は別々に合計し、それぞれ1回ずつ端数処理します。 税率をまたいで合計してから丸めるのも誤りです。

2. 丸め方は選べるが、継続適用する

切上げ・切捨て・四捨五入のどれを採るかは事業者が決められます。 ただし請求書ごとに変えてよいという意味ではありません。実装上は 事業者の設定として持ち、請求書単位で上書きできないようにするのが安全です。

3. 浮動小数点を使わない

0.1 は二進数で正確に表現できません。金額計算に float を使うと、 丸めの直前で 31.499999... のような値になり、四捨五入の結果が変わることがあります。 PHP なら BCMath、文字列と整数で扱ってください。

PHP での実装例

use Foovar\JpInvoice\{Issuer, Invoice, LineItem};
use Foovar\JpInvoice\Enum\{TaxRate, RoundingMode};

$issuer  = new Issuer('自社', 'T1234567890123', RoundingMode::FLOOR);
$invoice = new Invoice($issuer);

$invoice->addLine(new LineItem('商品A', '1', '105', TaxRate::STANDARD_10));
$invoice->addLine(new LineItem('商品B', '1', '105', TaxRate::STANDARD_10));
$invoice->addLine(new LineItem('商品C', '1', '105', TaxRate::STANDARD_10));

$result = $invoice->calculate();
$result->totalTaxAmount;   // 31(行ごとに丸めた 30 ではない)

ブラウザ上のデモで、自分の明細を入れて差を確認できます。

foovar/jp-invoice — インボイス対応の消費税計算ライブラリ(MIT・無料)

composer require foovar/jp-invoice
PHP 8.2+/依存パッケージなし/float 不使用/国税庁Q&A の計算例をテストに採録。 GitHub