このルーレットは本当にランダム?
はい。結果はアニメーションが始まる前に、ブラウザの暗号論的乱数で決まっています。サーバーが結果を操作することはなく、自分で確率を設定しない限り、どの項目も当たる確率は同じです。
const MAX = 2 ** 32
function randomInt(n) {
// 割り切れない端数を捨て、n 通りすべてを完全に同じ確率にする
const limit = MAX - (MAX % n)
let value
do {
value = crypto.getRandomValues(new Uint32Array(1))[0]
} while (value >= limit)
// これで剰余に偏りがなくなる
return value % n
}当サイト共通の乱数モジュールにある randomInt 関数です。ブラウザの開発者ツールの「ソース」パネルで全文を確認できます。
試してみよう:10,000 回シミュレーション
同じ大きさの項目が 8 個。公平なルーレットなら各項目に約 1,250 回ずつ当たり、自然な小さなぶれが出ます。
点線:完全に公平なルーレットでの、その項目の期待回数
均等モードでは、左に載せた randomInt 関数を1回ごとに呼び出しています。すべてブラウザ内で動きます。 カイ二乗検定は、各棒が期待値からどれだけ離れているかを合計したものです。公平な乱数でも約20回に1回は 5% の基準を超えるので、1回の不合格は想定内。毎回不合格になるなら問題があるということです。
- プレゼント企画や配信の抽選当選者がどう選ばれるかを事前に見せれば、「出来レースでは」という疑いを減らせます。
- 授業での指名先生にも結果を操作できないと分かれば、指名された生徒も納得しやすくなります。
- グループでの決めごとランチ、掃除当番、誰がおごるか。「仕込みでしょ」と言われたらこのページを見せてください。
- 確率設定の確認30% の項目が長い目で見て10回に3回ほど当たる理由と、短期ではぶれる理由が分かります。
「回す」を押したときに起きていること
「回す」を押す
ページがブラウザの crypto.getRandomValues に新しい 32 ビットの乱数を要求します。OS の安全な乱数源から作られる値です。
止まる位置がその場で決まる
乱数で何周回るかと最終的な角度が決まります。この時点で結果は確定済み。止まったときに針の下にある項目が当選です。
それからアニメーション
回転、減速、カチカチ音は演出だけ。長く見ても、速い端末で回しても、決まっている停止角度は変わりません。
データは端末の外に出ない
抽選はブラウザ内で行われ、結果を決めるためにリストがサーバーへ送られることはありません。裏で操作するスイッチは存在しません。
公平さを支える6つの仕組み
単純な剰余が少しだけ不公平な理由と、棄却サンプリングによる解決
乱数生成器は決まった範囲の整数を返します。ブラウザが返すのは 32 ビット整数で、4,294,967,296 通り。n 個の中から1つ選ぶ一番簡単な方法は value % n、つまり n で割った余りを使うことです。ただし 2 の 32 乗が n で割り切れない限り、いくつかの余りだけが1回ぶん多く出てしまいます。
0 から 7 までしか出ない小さな生成器で、3つの中から選ぶ例を見ると分かりやすいでしょう。
| 方式 | 項目 1 | 項目 2 | 項目 3 |
|---|---|---|---|
| 単純な剰余、値は 0 から 7 | 0、3、6:8分の3 | 1、4、7:8分の3 | 2、5:8分の2だけ |
| 棄却サンプリング、6 と 7 は捨てて引き直す | 0、3:6分の2 | 1、4:6分の2 | 2、5:6分の2 |
当サイトの randomInt は、2 の 32 乗から余りを引いた limit を計算し、limit 以上の値が出たら捨てて引き直します。32 ビットならもともとの偏りはごくわずかで、1クラス分の名簿なら1億分の1未満、引き直しもほぼ起きませんが、結果は「ほぼ正確」ではなく「完全に正確」になります。
ルーレットは乱数をどうやって当選結果にしているか
ルーレットは、こっそり番号を選んでから回っているふりをしているわけではありません。実際の停止角度を乱数で決めています。5〜8周の間のランダムな回転数と、ランダムな最終角度。どちらも crypto.getRandomValues から得た値です。各項目は円の一部を占めているので、針の下で止まる確率は、その項目が 360 度に占める割合とぴったり一致します。誤差はおよそ 40 億分の 1 です。
項目に確率をパーセントで設定すると、その割合で円が描き直されます。合計が 100 にならないと回せません。50% の項目は円の半分を占め、長い目で見ると約半分の回で当たります。ルーレットを使わずリストから選ぶツールは weightedIndex を使い、重みの合計の上にランダムな点を取って、どの項目の範囲に入るかで決めます。
- 回す前にルーレットがどの位置で止まっていても関係ありません。最終角度は出発点に関係なく一様に分布します。
- 回転速度や時間はアニメーションを長くするだけ。停止角度は最初のフレームより前に決まっています。
- 当選者を外すとルーレットが描き直され、残った項目で円を分け合います。
連続当選は普通のこと:本物のランダムの姿
抽選ルーレットへの一番多い不満は「また同じ人?」というもの。公平な乱数には記憶がないので、連続は起こりうるどころか、起きて当然です。次の回が前回と同じ結果になる確率は、単純に 1 を項目数で割った値です。
| ルーレットの項目数 | 2回連続で同じ結果 | 20回回して1度でも連続が出る確率 |
|---|---|---|
| 2 | 50% | ほぼ確実 |
| 4 | 25% | 約 99.6% |
| 8 | 12.5% | 約 92% |
| 12 | 8.3% | 約 81% |
| 30 | 3.3% | 約 47.5% |
全員に一度ずつ順番が回ってから重複させたいなら、それは「より公平な抽選」ではなく「非復元抽出」です。名前ルーレットの「外してもう一度回す」を使うか、リストシャッフルで名簿を一度に並べ替えましょう。
Math.random を使っている場所と、結果に影響しない理由
Math.random は演出には十分なので、いくつかのアニメーションで使っています。サイコロのわずかな傾き、マーブルレースの揺れ、数字が決まる前のパラパラ表示などです。どの場合も結果そのものは暗号論的乱数ですでに決まっており、演出は見た目の順位が変わらない程度に抑えています。
共通の乱数モジュールが Math.random に切り替わるのは、ブラウザが Web Crypto をまったく持っていない場合だけで、今ではかなり古いブラウザに限られます。現在の Chrome、Safari、Firefox、Edge、LINE のアプリ内ブラウザはいずれも crypto.getRandomValues に対応しています。
よくある質問
抽選ルーレットは本当にランダムですか?
当サイトのルーレットはランダムです。毎回ブラウザの暗号論的に安全な乱数 crypto.getRandomValues を使い、アニメーションの前に止まる位置を決めています。各項目の当選確率は面積に比例し、同じ大きさの項目は同じ確率です。
サイト運営者や回す人がイカサマできますか?
抽選はブラウザ内で行われるため、当サイトのサーバーが結果を決めたり変えたりすることはありません。回す人も操作できません。押すタイミングや長押しは、ランダムな停止角度に影響しません。コードはブラウザの開発者ツールでいつでも確認できます。
同じ名前が2回連続で当たったのはなぜ?
乱数には記憶がないからです。8 項目なら同じ項目が2回続く確率は 12.5%、20回回せば一度は連続が起きる確率が約 92% あります。重複させたくない場合は、当選した項目を毎回外してください。
長く回したり速く回したりすると確率は変わりますか?
変わりません。停止角度は「回す」を押した瞬間に決まります。時間、速度、減速のかかり方は見た目が変わるだけで、止まる場所は変わりません。
ルーレットの確率設定はどう動いていますか?
項目にパーセントを入れると、その割合ぶん円を占めるように描かれます。合計は 100 にする必要があります。30% の項目は長い目で見ると10回に3回ほど当たりますが、短期的にはそれより多くも少なくもなります。
シミュレーターのカイ二乗値と判定はどういう意味ですか?
各項目の実際の回数と期待回数の差を二乗し、期待回数で割って合計したものがカイ二乗値です。その項目数に対応する 5% の基準値を下回れば、公平な乱数と矛盾しない結果です。公平でも約20回に1回は不合格になるので、何度か試してから判断してください。
当選者を Math.random で決めていますか?
いいえ。Math.random はサイコロの傾きやマーブルの揺れなど、結果が出た後の演出にだけ使っています。結果は crypto.getRandomValues から得ており、Web Crypto に対応していない非常に古いブラウザでのみ Math.random に切り替わります。