どの言語で起きるか
シルギアのツールは JavaScript で書かれています。この不具合が見つかったケースを、CODE Polyglot で10言語の「よくある書き方」で回した結果です(言語を返した答えごとにまとめ、赤い値は実際に返した間違った答え)。 JavaScript は 3件中3件で外れました。
| ケース | 正解 | よくある書き方の答え(10言語) |
|---|---|---|
| c010逆算・20%で36%にちょうど届く |
|
|
| c011逆算・10%で27.1%にちょうど届く |
|
|
| c012逆算・90%で99.9%にちょうど届く |
|
|
何が起きていたか
ガチャの確率を計算するツールの「逆算」(目標の確率で当てるには何回引けばよいか)で、必要な回数が1回多くなることがありました。
- 排出率20%で目標36%なら、2回引けば 1 − 0.8² = 0.36 で、ちょうど36%に届きます。答えは 2回 です。ツールは 3回 と表示していました
- 排出率10%・目標27.1%(正しくは3回)は 4回、排出率90%・目標99.9%(正しくは3回)も 4回 でした
ずれるのは、何回か引いたときの確率が、目標にちょうど等しくなる入力です。
どのくらいの入力で起きていたか
直す前の式を Node でそのまま動かし、分数で計算した正確な答えと比べました。範囲は、排出率 0.1〜99.9%(0.1%刻み・999種)× 目標 0.1〜99.9%(0.1%刻み・999種)の 998,001通り です。
| ずれ | 件数 |
|---|---|
| 必要回数が1回多い | 5通り |
| 必要回数が少ない | 0通り |
この範囲で、2回以上引いて目標にちょうど届く組み合わせは18通りです。そのうち5通り(排出率10%・目標27.1%、20%・36%、20%・48.8%、70%・91%、90%・99.9%)で1回多くなっていました。残りの997,996通りは正しい回数でした。
どうやって見つかったか
CODE Polyglot の仕様 ガチャの当選確率と必要回数 で、正解表に「目標にちょうど届く」ケース c010〜c013 を入れました(2026-10-09)。よくある書き方は4言語(PHP・Python・Go・JavaScript)とも、c010〜c012 の3件で1回多い答えを返しました。
元のツールも同じ式でした。公開中のシルギアで「排出率20%・目標36%」が3回と表示されることを、シルギア側が本番のブラウザで再現しました。
原因
必要回数は ⌈log(1 − 目標) ÷ log(1 − 排出率)⌉ で求めていました。数学の上では、20%・36% は log(0.64) ÷ log(0.8) = 2 ちょうどです。2進数の小数で計算すると、商が 2.0000000000000004 と 2 よりわずかに大きくなり、切り上げで3回になります。
どう直したか
計算の部分を calc.js に分けました。log の計算は「見当を付ける」ためだけに使います。商が整数にごく近いときだけ、「(1 − 排出率)^N ≤ 1 − 目標」を分数(BigInt)で正確に比べて、回数を決めます。
- var n = Math.ceil(Math.log(1 - target) / Math.log(1 - p)); + var n = requiredTrials(rateEl.value, targetEl.value);
+ const x = Math.log(1 - t) / Math.log(1 - p); + const k = Math.round(x); + // (1 − p)^N ≤ 1 − t を厳密判定: qn^N · ad ≤ an · qd^N + return qn ** NB * ad <= an * qd ** NB; + if (Math.abs(x - k) < EPS && k >= 1 && k <= EXACT_MAX) { + N = satisfies(k) ? k : k + 1; + } else { + N = Math.ceil(x); + }
ちょうど届くのは回数が小さいときに限られます。そのため、10万回を超えるときは正確な比較をせず、計算が重くならないようにしました。
確かめたこと
- 新しい
calc.jsを、排出率36種 × 目標999種の35,964通りで正確な答えと突き合わせ、35,964件中35,964件が一致(2026-10-10) - 修正後の本番のページをヘッドレス Chrome で開き、20%・36% が2回、10%・27.1% が3回、90%・99.9% が3回と表示されることを確認
- シルギア側のテスト10件が、すべて通過