CODE Polyglot / 直した不具合

利息が1円少なくなり、利息・税額・税引後の利息の内訳がそろわない

10万円・年0.5%・2年・複利 → 利息1,002円(正しくは1,003円)。8,100通り中1,445通りで「利息 − 税額 ≠ 税引後の利息」

どの言語で起きるか

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

ケース正解よくある書き方の答え(10言語)
c005利息がちょうど 3.5円
gross_total
1004
interest
4
tax
0
net_interest
4
net_total
1004
  • PHP・Python・Go・JavaScript・Java・Rust・Ruby・Kotlin・Swiftgross_total1003interest3net_interest3net_total1003
  • C#✓ 正解
c006複利の利息がちょうど 1,002.5円
gross_total
101003
interest
1003
tax
203
net_interest
800
net_total
100800
  • PHP・Python・Go・JavaScript・Java・C#・Rust・Ruby・Kotlin・Swiftgross_total101002interest1002net_interest799net_total100799
c007複利の利息がちょうど 50,062.5円
gross_total
10050063
interest
50063
tax
10170
net_interest
39893
net_total
10039893
  • PHP・Python・Go・JavaScript・Java・C#・Rust・Ruby・Kotlin・Swiftgross_total10050062interest50062tax10169

何が起きていたか

元本・年利・年数から利息と税引後の金額を出すツールで、3つの点がずれていました。

  • 内訳がそろわない: 利息・税額・税引後の利息・元利合計を、それぞれ別々に円へ四捨五入して表示していたので、「利息 − 税額 = 税引後の利息」にならないことがありました
  • 利息が1円少ない: 10万円を年0.5%の複利で2年預けると、利息は 1,002.5円で、四捨五入して 1,003円が正解です。ツールは 1,002円 と表示していました
  • 税額の計算が実務と違う: 利息 × 20.315% を1回で計算していました。銀行の源泉分離課税は、所得税・復興特別所得税 15.315% と住民税 5% を、それぞれ1円未満切り捨てにします

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

元本9通り(1万〜1,000万円)× 年利15通り(0.001〜5%)× 1〜30年 × 単利・複利 の 8,100通り で調べました(2026-10-16)。

ずれ件数
「利息 − 税額 ≠ 税引後の利息」になる(正確に計算しても起きる)1,445通り
利息が1円少ない(2進数の小数の誤差)9通り

内訳のずれは、2進数の誤差ではなく、丸め方の設計によるものです。正確な値で計算しても、別々に四捨五入すると合計が合わなくなります。

どうやって見つかったか

CODE Polyglot の仕様 預金利息の計算 を作る前に、元のツールの式を正確な値と比べました(2026-10-16)。利息の1円ずれより、内訳のずれの方がずっと多いことが分かったので、仕様では税を実務どおりに決め、内訳が必ずそろう定義にしました。

原因

  • 複利の principal * Math.pow(1 + r, years) は、2進数の小数で計算すると、1,002.5 が 1002.4999999999709 になります(解説)
  • 表示用の関数 yen() が、利息・税額・税引後の利息のそれぞれを Math.round していたので、3つの数の関係が崩れていました

どう直したか

計算の部分を calc.js に分け、年利を分数(BigInt の分子・分母)にして、利息を正確に求めてから1回だけ円に四捨五入するようにしました。税額と税引後の金額は、その円の利息から整数で求めるので、内訳が必ずそろいます。

- var TAX_RATE = 0.20315; // 所得税・復興特別所得税・住民税
- grossTotal = principal * Math.pow(1 + r, years);
- var tax = interest * TAX_RATE;
- var netInterest = interest - tax;
+ // 整数年の年複利は BigInt 冪で厳密。利息 = pn×((rd+rn)^y − rd^y) / (pd×rd^y)
+ interest = roundYen(pn * (powBase - powRd), pd * powRd);
+ var tax = taxOf(interest);
+ var net = interest - tax;
+ function roundYen(num, den) {
+   return (2n * num + den) / (2n * den);
+ }
+ function taxOf(interest) {
+   return (interest * 15315n) / 100000n + (interest * 5n) / 100n;
+ }

画面の税額の表記も「税額(所得税等15.315%・住民税5%)」に直しました。

確かめたこと

  • 修正後の本番の calc.js(コミット c541ae64)で、整数の年数の範囲の14,111通り(無作為6,000通り・上の8,100通り・正解表11件)がすべて正解と一致(2026-10-17)
  • シルギア側のテスト15件と、ツール全体のテスト291件がすべて通過
  • 本番の画面で、10万円・年0.5%・2年・複利 → 利息1,003円・税203円・税引後の利息800円、100万円・年0.2%・10年・複利 → 利息20,181円・税4,099円 と表示されることを確認(シルギア側の報告)

← 直した不具合の一覧へ