どの言語で起きるか
シルギアのツールは JavaScript で書かれています。この不具合が見つかったケースを、CODE Polyglot で10言語の「よくある書き方」で回した結果です(言語を返した答えごとにまとめ、赤い値は実際に返した間違った答え)。 JavaScript は 4件中4件で外れました。
| ケース | 正解 | よくある書き方の答え(10言語) |
|---|---|---|
| c004小数の単価で .5 ちょうど(浮動小数点で下回る) |
|
|
| c006小数の使用量 |
|
|
| c007上水道の単価で .5 ちょうど(浮動小数点で下回る・その2) |
|
|
| c008下水道だけ小数の使用量で .5 ちょうど |
|
|
何が起きていたか
使用量と単価から水道料金を出すツールで、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件がすべて通過