CODE Polyglot / 直した不具合

確率の表示が小数第3位で1ずれる

0.5%を2回 → 0.997%(正しくは0.998%)

どの言語で起きるか

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

ケース正解よくある書き方の答え(10言語)
c0030.5% を2回・.xxx5 が2進で少し小さくなる
at_least_one
0.998
none
99.003
expected
0.010
  • PHP・Java・Kotlinat_least_one"0.997"
  • Python・Go・JavaScript・C#・Rust・Ruby・Swiftat_least_one"0.997"none"99.002"
c009期待回数 .xxx5 が2進で少し小さくなる
at_least_one
0.350
none
99.650
expected
0.004
  • PHP・Python・Go・JavaScript・Java・C#・Rust・Kotlin・Swiftexpected"0.003"
  • Ruby✓ 正解

何が起きていたか

ガチャの確率を計算するツールで、確率と期待回数の表示が、小数第3位で1少なくなることがありました。

  • 排出率0.5%で2回引いて1回以上当たる確率は 0.9975% ちょうどで、四捨五入して 0.998% が正解です。ツールは 0.997% と表示していました。1回も当たらない確率(99.0025% → 99.003% が正解)も 99.002% でした
  • 排出率0.35%で1回引いたときの当たりの期待回数は 0.0035回で、0.004回が正解です。ツールは 0.003回 と表示していました

同じ修正で、もう1つの表示も直しました。100%未満の確率は「99.999%超」と出すのがツールの決まりです。ところが試行回数が多いと、「100%」 と表示していました(例: 排出率50%で60回)。

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

直す前の表示の式を Node でそのまま動かし、分数で計算した正確な値を同じ決まりで表示したものと比べました。範囲は、排出率 0.1〜99.9%(0.1%刻み・999種)× 試行回数 1〜100回 の 99,900通り です。

ずれ件数
1回以上当たる確率が、小数第3位で1少ない50通り
1回も当たらない確率が、小数第3位で1少ない53通り
1回も当たらない確率がちょうど 0.001% なのに「0.001%未満」1通り
当たりの期待回数がずれる0通り
1回以上当たる確率が「99.999%超」でなく「100%」41,099通り

期待回数は、排出率が小数第1位までならずれませんでした。排出率を 0.01〜99.99%(0.01%刻み・9,999種)× 試行回数 1〜100回 の 999,900通り に広げると、44,449通りで期待回数が小数第3位で1少なくなりました(例: 0.03%で5回 → 0.001回、正しくは 0.002回)。

「100%」の表示は、排出率が高いほど、試行回数が多いほど出ます。

どうやって見つかったか

CODE Polyglot の仕様 ガチャの当選確率と必要回数 で、正解表に「小数第4位がちょうど5」のケースを入れました(2026-10-09)。0.5%で2回(c003)と、0.35%で1回の期待回数(c009)は、2進数の小数では5より少し小さくなる値です。よくある書き方は4言語(PHP・Python・Go・JavaScript)とも、この2件で外れました。

元のツールも同じ toFixed(3) で表示していました。必要回数の不具合(別のページ)を先に直し、表示は同じ日(2026-10-10)に直しました。

原因

0.5%で2回の確率を2進数の小数で計算すると、0.9975% ではなく 0.9974999999999956% になります。toFixed(3) はこの値を丸めるので、0.997 に切り下がります。期待回数の 0.0035 も、0.0034999999999999996 になって 0.003 になります。

「100%」は、1 − (1 − 排出率)^N を小数で計算したためです。(1 − 排出率)^N が非常に小さくなると、1 から引いても 1 のままになります。ツールは 100 ちょうどを「99.999%超」の対象にしていなかったので、そのまま「100%」と出ていました。

どう直したか

確率と期待回数を、calc.js で分数(BigInt)のまま計算し、整数で小数第3位に四捨五入するようにしました。表示の決まり(99.999%超・0.001%未満・末尾の0を出さない)は変えていません。

- var rounded = Number(pct.toFixed(3));
- var pAtLeastOne = 1 - Math.pow(1 - p, n);
- var expected = n * p; // 期待当選回数
+ var atLeastStr = percentAtLeastOne(rateEl.value, n);
+ var expectedStr = expectedCount(rateEl.value, n);
+ return { n: q.n ** M, d: q.d ** M };
+ if (num > 0n && num < den && num * 100000n >= 99999n * den) return "99.999%超";
+ const units = roundHalfUpDiv(num * 100000n, den); // round(P×100000) = round(pct×1000)
+ return (2n * num + den) / (2n * den);

試行回数が多く、正確な分数の桁数が4,000桁を超えるときは、18桁の固定小数点で近似して、計算が重くならないようにしました。そのときも、排出率が100%未満なら「100%」「0%」とは出さず、「99.999%超」「0.001%未満」と出します。

確かめたこと

  • 本番が配信している calc.js を、排出率25種 × 試行回数304種の7,600通りで正確な値と突き合わせ、7,600件中7,600件が一致(2026-10-10)
  • 最初に直した版は、試行回数が大きいときに「0%」「100%」と表示する誤りがありました(7,600件中19件が不一致)。この突き合わせで見つけて差し戻し、上の修正(c869ac0f)で直りました。シルギア側のテスト表(11件)だけでは見つかりませんでした
  • シルギア側のテスト28件が、すべて通過

← 直した不具合の一覧へ