CODE Polyglot / 直した不具合

税込からの割戻しで税抜が1円少なくなる

税込1,100円・10%・切り捨て → 税抜999円(正しくは1,000円)

どの言語で起きるか

シルギアのツールは JavaScript で書かれています。この不具合が見つかったケースを、CODE Polyglot で10言語の「よくある書き方」で回した結果です(言語を返した答えごとにまとめ、赤い値は実際に返した間違った答え)。 JavaScript は 3件中3件で外れました。

ケース正解よくある書き方の答え(10言語)
c010税込1100円から割戻し・10%・切り捨て
exclusive
1000
tax
100
inclusive
1100
  • PHP・Python・Go・JavaScript・Java・Rust・Ruby・Kotlin・Swiftexclusive999tax101
  • C#✓ 正解
c011税込1080円から割戻し・8%・切り捨て
exclusive
1000
tax
80
inclusive
1080
  • PHP・Python・Go・JavaScript・Java・Rust・Ruby・Kotlin・Swiftexclusive999tax81
  • C#✓ 正解
c012税込110円から割戻し・10%・切り捨て
exclusive
100
tax
10
inclusive
110
  • PHP・Python・Go・JavaScript・Java・Rust・Ruby・Kotlin・Swiftexclusive99tax11
  • C#✓ 正解

何が起きていたか

請求書の消費税を計算するツールで、税込の金額から税抜を求める(割戻し)と、税抜が1円少なくなることがありました。

  • 税込1,100円・10%・切り捨ての税抜は、1,100 ÷ 1.1 = 1,000円ちょうどです。ツールは 999円(税額101円)と表示していました
  • 税込1,080円・8%(正しくは1,000円)も 999円、税込110円・10%(正しくは100円)も 99円 でした

ずれるのは、割り切れて税抜がちょうど整数になる金額です。割り切れない金額や、税抜から税額を求める計算では起きていませんでした。

どのくらいの入力で起きていたか

直す前の式を Node でそのまま動かし、分数で計算した正確な値と比べました。範囲は、金額 1〜100,000円(1円刻み)× 税率 8%・10% × 端数処理3種(切り捨て・四捨五入・切り上げ)です。

計算調べた数ずれた数
税込から税抜(割戻し)・10%・切り捨て100,000通り4,174通り
税込から税抜(割戻し)・8%・切り捨て100,000通り818通り
税込から税抜(割戻し)・四捨五入・切り上げ400,000通り0通り
税抜から税額600,000通り0通り

ずれたのはすべて、割り切れる金額でした。10%で割り切れる税込金額(11の倍数)は100,000通り中9,090通りあり、そのうち4,174通りで1円少なくなっていました。8%(27の倍数)は3,703通り中818通りです。

どうやって見つかったか

CODE Polyglot の最初の仕様 請求書の消費税と端数処理 で、正解表の c010〜c012(税込1,100円・1,080円・110円からの割戻し)を作りました(2026-10-09)。PHP と Python のよくある書き方が、3件とも1円少ない答えを返しました。

元のツールの式(Math.floor(inclusive / (1 + rate)))を Node で動かすと、同じく 999円 になりました。その日のうちに、シルギア側で直しました。

原因

0.1 や 1.1 は、2進数の小数では正確に表せません。そのため 1100 / 1.1 は 1000 ではなく 999.9999999999999 になり、切り捨てると 999 になります(解説)。

どう直したか

計算の部分を calc.js に分け、金額と税率を十進の文字列のまま分数(BigInt の分子と分母)にしました。割り算は整数の商で端数処理するので、2進数の小数を一度も通りません。

- var rate = pct / 100;
- exclusive = applyRounding(inclusive / (1 + rate), mode);
+ return { n: BigInt(intPart + fracPart), d: 10n ** BigInt(fracPart.length) };
+ const rate = divFrac(pct, { n: 100n, d: 1n });
+ const onePlusRate = addFrac({ n: 1n, d: 1n }, rate);
+ const exclusiveInt = roundToYen(divFrac(inclusive, onePlusRate), mode); // BigInt 円
+ if (mode === "ceil") return (n + d - 1n) / d;
+ if (mode === "round") return (2n * n + d) / (2n * d); // floor(n/d + 1/2)
+ return n / d; // floor(既定: 切り捨て)

任意の税率(7.5% など)も、十進のまま計算します。

確かめたこと

  • シルギア側のテスト20件が、すべて通過(CODE Polyglot から実行して確認・2026-10-09)
  • 正解表19件のうち、元のツールが受け付ける範囲(負の金額と 2^53 を超える金額を除く)の14件を新しい calc.js に通し、14件中14件が一致
  • 修正後の本番のページをヘッドレス Chrome で操作し、税込1,100円・1,080円・110円の割戻しが 1,000円・1,000円・100円 と表示されることを確認(2026-10-09)

← 直した不具合の一覧へ