どの言語で起きるか
シルギアのツールは JavaScript で書かれています。この不具合が見つかったケースを、CODE Polyglot で10言語の「よくある書き方」で回した結果です(言語を返した答えごとにまとめ、赤い値は実際に返した間違った答え)。 JavaScript は 3件中2件で外れました。
| ケース | 正解 | よくある書き方の答え(10言語) |
|---|---|---|
| c004表示の境目(浮動小数点で下回る) |
|
|
| c005表示の境目(浮動小数点で下回る・その2) |
|
|
| c006表示の境目(偶数丸めで割れる) |
|
|
何が起きていたか
2つの商品の単価を比べて「どちらがお得か」を出すツールで、本当は同じ単価なのに、片方を「お得」と判定していました。
- 商品A 97円・80g、商品B 1,213円・1,000g: 100gあたりの単価は、A が 97 ÷ 80 × 100 = 121.25円、B が 121.3円です。A を小数第1位に四捨五入すると 121.3円なので、正解は「どちらも同じ」です。ツールは A を 121.2円 と表示し、「商品A がお得(0.1円安い)」 と判定していました
- 169円・80g(211.25円)と 2,113円・1,000g(211.3円)の組でも、同じように「商品A がお得」でした
どのくらいの入力で起きていたか
価格 1〜1,000円(1円刻み)× 内容量 1〜1,000g(1g刻み)の 1,000,000通り で、100gあたりの単価を正確な値と比べました。
| ずれ | 件数 |
|---|---|
| 単価の表示が 0.1円少ない | 1,000,000通り中155通り |
| 単価の表示が 0.1円多い | 1,000,000通り中0通り |
この1,000,000通りから2つを選ぶ組み合わせで、判定も数えました。
| ずれ | 件数 |
|---|---|
| 本当は同じ単価なのに「A(または B)がお得」 | 本当は同じ単価の 166,955,732組中41,933組 |
| 本当は 0.1円違うのに「どちらも同じ」 | 本当は 0.1円違う 333,114,888組中41,886組 |
どうやって見つかったか
CODE Polyglot の仕様 単価の比較(どちらがお得か) を作るとき、元のツールと同じ式を4言語で書いて、正確な値と突き合わせました(2026-10-10)。正解表11件のうち、表示の境目を狙った c004(97円・80g)と c005(169円・80g)を、4言語とも「A がお得」と答えました。元のツールと同じ式を Node で評価すると、公開中のツールも同じ答えになることがわかりました。
原因
- 97 ÷ 80 × 100 を2進数の小数で計算すると、121.25 ではなく 121.24999999999999 になります。0.1円単位で四捨五入すると、0.05 に届かないので 121.2 に切り下がります(解説)
- ツールは「表示した桁で同じかどうか」を判定していました。そのため、0.1円の切り下がりがそのまま判定の逆転になりました
どう直したか
計算の部分を calc.js に分けました。価格と内容量を十進の文字列のまま整数の分子・分母(BigInt)にし、「単価 × 10」を整数で四捨五入します。判定は、四捨五入した単価どうしで行います。
- var unitA = (ap / aq) * info.base; - var ra = Math.round(unitA * 10); - var rb = Math.round(unitB * 10); - var diff = Math.abs(unitA - unitB); + const tenthsA = roundHalfUpDiv(aP.n * aQ.d * base * 10n, aP.d * aQ.n); + const tenthsB = roundHalfUpDiv(bP.n * bQ.d * base * 10n, bP.d * bQ.n); + if (tenthsA === tenthsB) winner = "same"; + else winner = tenthsA < tenthsB ? "A" : "B"; + const diffT = tenthsA > tenthsB ? tenthsA - tenthsB : tenthsB - tenthsA;
+ // round(num/den)(num,den>0・0.5は繰り上げ) + function roundHalfUpDiv(num, den) { + return (2n * num + den) / (2n * den); + }
「◯円安い」の差も、丸める前の単価の差ではなく、丸めた単価どうしの差にしました。判定と差の表示が食い違わないようにするためです。
確かめたこと
- 本番が配信している
calc.js(リポジトリと同じ内容、コミット 76c8844f)で、無作為な100,000通りがすべて正解と一致(判定「どちらも同じ」630件を含む。2026-10-11) - 本番のページをブラウザ(ヘッドレス Chrome)で開き、97円・80g と 1,213円・1,000g で「どちらも同じ」と表示されることを確認
- シルギア側のテスト14件がすべて通過