CODE Polyglot / 直した不具合

単価に小数があると料金が1円ずれ、内訳を足しても合計と合わない

15m³ × 16.9円 → 上水道253円(正しくは254円)

どの言語で起きるか

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

ケース正解よくある書き方の答え(10言語)
c004小数の単価で .5 ちょうど(浮動小数点で下回る)
water
254
sewer
0
subtotal
254
tax
25
total
279
  • PHP・Python・Go・JavaScript・Java・Rust・Ruby・Kotlin・Swiftwater253subtotal253total278
  • C#✓ 正解
c006小数の使用量
water
32
sewer
0
subtotal
32
tax
3
total
35
  • PHP・Python・Go・JavaScript・Java・Rust・Ruby・Kotlin・Swiftwater31subtotal31total34
  • C#✓ 正解
c007上水道の単価で .5 ちょうど(浮動小数点で下回る・その2)
water
485
sewer
0
subtotal
485
tax
49
total
534
  • PHP・Python・Go・JavaScript・Java・C#・Rust・Ruby・Kotlin・Swiftwater484subtotal484tax48total532
c008下水道だけ小数の使用量で .5 ちょうど
water
0
sewer
448
subtotal
448
tax
45
total
493
  • PHP・Python・Go・JavaScript・Java・Rust・Ruby・Kotlin・Swiftsewer447subtotal447total492
  • C#✓ 正解

何が起きていたか

使用量と単価から水道料金を出すツールで、2つのずれがありました。

  • 料金が1円少なくなる: 使用量 15m³ × 従量単価 16.9円 の上水道料金は 253.5円で、四捨五入して 254円が正解です。ツールは 253円 と表示していました
  • 内訳を足しても合計と合わない: 同じ入力(基本料金0円・下水道0円・税込)で、ツールは上水道 253円・下水道 0円・消費税 25円と表示し、合計を 279円 と表示していました。内訳を足すと 278円です

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

1円のずれは、上水道1行だけで数えました。基本料金 0円、使用量 1〜100m³(1m³刻み)× 従量単価 0.1〜300.0円(0.1円刻み)の 300,000通り です。

ずれ件数
上水道の料金が1円少ない300,000通り中692通り
上水道の料金が1円多い300,000通り中0通り

内訳と合計の不一致は、上水道・下水道の両方に値を入れて数えました。無作為に選んだ 100,000通り(使用量 1〜100m³ の整数、基本料金 0〜3,000円の10円刻み、従量単価 0.0〜500.0円の0.1円刻み。上水道・下水道それぞれ。税込表示)です。

ずれ件数
上水道の料金が1円ずれる100,000通り中84通り
下水道の料金が1円ずれる100,000通り中95通り
表示した上水道・下水道・消費税の和が、表示した合計と合わない100,000通り中31,146通り

内訳と合計の不一致は、1円のずれよりずっと多く起きていました。

どうやって見つかったか

CODE Polyglot の仕様 水道料金(上水道・下水道・消費税) を作るとき、元のツールと同じ式を4言語で書いて、正確な値と突き合わせました(2026-10-10)。正解表13件のうち、c004(15m³ × 16.9円)・c006・c007・c008 を4言語とも1円少なく答えました。元のツールと同じ式を Node で評価すると、公開中のツールも 15m³ × 16.9円で上水道 253円、内訳の和 278円・合計 279円になることがわかりました。

原因

  • 16.9 のような小数の単価は、2進数の小数では正確に表せません。15 × 16.9 は 253.49999999999997 になり、0.5 に届かないので四捨五入で切り捨てられます(解説)
  • ツールは、上水道・下水道・消費税・合計を丸める前の値から別々に計算し、表示のときにそれぞれ四捨五入していました。そのため、表示した内訳を足しても合計と合わないことがありました

どう直したか

計算の部分を calc.js に分けました。使用量と単価を十進の文字列のまま整数の分子・分母(BigInt)にして、上水道・下水道をそれぞれ円に四捨五入します。消費税は、丸めた上水道と下水道の和にかけます。合計は「税抜合計 + 消費税」なので、内訳の和と必ず一致します。

- var waterUsageFee = usage * waterUnit;
- var waterFee = waterBase + waterUsageFee; // 上水道(税抜)
- var tax = incl ? subtotal * TAX_RATE : 0;
- var total = subtotal + tax;
- return Math.round(n).toLocaleString("ja-JP") + "円";
+ const waterFee = feeOf(WB, U, WU); // 円(整数・四捨五入)
+ const sewerFee = feeOf(SB, U, SU);
+ const subtotal = waterFee + sewerFee; // 税抜合計
+ const tax = hasTax ? roundHalfUpDiv(subtotal, TAX_RATE_DEN) : 0n;
+ const total = subtotal + tax; // 内訳の和と一致

使用量別の早見表も、同じ計算で出すようにしました。

確かめたこと

  • 本番が配信している calc.js(リポジトリと同じ内容、コミット d6dac272)で、無作為な100,000通りがすべて正解と一致し、内訳の和と合計の不一致は0件(2026-10-11)
  • 本番のページをブラウザ(ヘッドレス Chrome)で開き、15m³ × 16.9円で上水道 254円・合計 279円(内訳の和と一致)と表示されることを確認
  • シルギア側のテスト16件がすべて通過

← 直した不具合の一覧へ