CODE Polyglot / 直した不具合

目標確率にちょうど届くとき、必要回数が1回多くなる

排出率20%・目標36% → 3回(正しくは2回)

どの言語で起きるか

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

ケース正解よくある書き方の答え(10言語)
c010逆算・20%で36%にちょうど届く
trials
2
actual
36.000
previous
20.000
expected
0.400
  • PHP・Python・Go・JavaScript・Java・C#・Rust・Ruby・Kotlin・Swifttrials3actual"48.800"previous"36.000"expected"0.600"
c011逆算・10%で27.1%にちょうど届く
trials
3
actual
27.100
previous
19.000
expected
0.300
  • PHP・Python・Go・JavaScript・Java・C#・Rust・Ruby・Kotlin・Swifttrials4actual"34.390"previous"27.100"expected"0.400"
c012逆算・90%で99.9%にちょうど届く
trials
3
actual
99.900
previous
99.000
expected
2.700
  • PHP・Python・Go・JavaScript・Java・C#・Rust・Ruby・Kotlin・Swifttrials4actual"99.990"previous"99.900"expected"3.600"

何が起きていたか

ガチャの確率を計算するツールの「逆算」(目標の確率で当てるには何回引けばよいか)で、必要な回数が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件が、すべて通過

← 直した不具合の一覧へ