ちいつる
A-10リファレンス

乱数・確率生成の設計

おみくじ・ルーレット・チーム分けの裏側にある公平性の設計思想を明かす。

触ってみるシードを1つずつ動かして、乱数列の形が隣のシードとどれだけ違うかを見てください
最初の3つ: 0.601, 0.448, 0.852

太線=シード42の列。薄い2本はすぐ隣のシード(4143)。1つずらすだけで形は無関係に飛ぶのに、同じシードに戻せば毎回この太線が再現される

太線を1本の乱数列だと思ってほしい。シードを1つ動かすだけで、すぐ隣に薄く残る前後のシードとは似ても似つかない形へ飛ぶ。なのに同じシードへ戻せば、寸分違わず同じ線が返ってくる。この「決まっているのにランダムに見える」性質こそが、疑似乱数を実用にしている。

PRNGの仕組みとシード値

コンピュータの「乱数」は厳密には決まった計算式で作られる疑似乱数(PRNG)で、同じ「シード値」から始めれば毎回同じ数列が再現される。Math.random()はシードを指定できないため、再現性が必要な場面(同じ条件で結果を検証したい、共有リンクで同じ結果を再現したい)では、シード指定可能な軽量PRNG(mulberry32等)を自前で用意する。

ただし「シードを指定できる」ことと「質が高い」ことは別問題だ。出力を1つずつ見ると均等に散らばっていても、隣り合う値の関係に隠れた規則性が残る実装もある。乱数の質は、1次元の分布だけでなく多次元の並びで検証する。

触ってみる隣り合う2つの乱数を座標にプロットして、格子構造の有無を見比べてください

各点=(uₙ, uₙ₊₁)。斜めの線は約 5 本 — 見た目は均等でも隣接値に構造が残る

値そのものは0〜1にきれいに散らばっているのに、(uₙ, uₙ₊₁)を打つと斜めの縞が浮かび上がる低品質な線形合同法(LCG)。次の値が前の値からほぼ計算できてしまうサインだ。良質なPRNGなら縞は消え、構造のない一様な雲になる。1次元で均等に見えても、それだけでは質は保証できない。

一様な乱数は、足し合わせるだけで別の分布を作り出す土台にもなる。偏りのない乱数源が1つあれば、そこから狙った形の分布を組み立てられる。

触ってみる足すサイコロの数を増やして、和の分布が釣鐘型に変わる様子を見てください

1個なら1〜6が均等(一様分布)

サイコロ1個の目は1〜6が平らに並ぶだけ。それを数個足すと、中央がこんもり盛り上がった釣鐘型(正規分布)に変わる。これが中心極限定理で、一様な乱数さえ手元にあれば正規分布や「自然っぽい揺らぎ」を組み立てられる。乱数演出でよく使う土台だ。

公平性の担保(おみくじ・ルーレット・チーム分け)

「公平」に見えるかどうかは、実際の確率だけでなく見た目の演出にも左右される。ルーレットは回転速度を毎回ランダムに変え、同じ位置で止まって見えないようにする。おみくじは結果の出現確率をテーブルで明示的に管理し、大吉のような希少な結果には低い確率を明示的に割り当てる。チーム分けはランダムに見えても、実際には人数の偏りが出ないよう「まず人数を均等に割ってから中身をシャッフルする」実装にする。

触ってみるスライダーで確率テーブルを書き換えて、おみくじを引いてみてください
大吉理論 5.0% / 実測 0回)
理論 30.0% / 実測 0回)
小吉理論 45.0% / 実測 0回)
理論 20.0% / 実測 0回)
累計 0

1回引いただけでは理論値から派手に外れる。ところが100回単位で引き足すと、実測の濃いバーが薄い理論バーへぴたりと寄っていく。確率テーブルを持つというのは、この寄っていく先の「理論値」を自分の手であらかじめ決めておくこと。数字を設計する作業そのものだ。

ルーレットのような演出付きの抽選では、「結果を決めるロジック」と「見せ方の演出」を分けて考えるのが要点だ。結果は先に一様乱数で公平に決めてしまい、回転量や速度といった演出だけを毎回ランダムに変える。こうすれば派手な演出と厳密な公平性を両立できる。

触ってみるルーレットを回して、演出が毎回違っても各区画の出現が均等になることを確かめてください
12345678
1
2
3
4
5
6
7
8

累計 0回 — 演出がどれだけ派手でも各区画の出現はほぼ均等(公平)

何周回るかも、止まるまでの時間も毎回バラバラ。それでも累計を数えると、各区画はほぼ同じ回数で出ている。なぜか。結果を先に一様乱数で決め、あとから派手な演出だけを乗せているからだ。こう切り分けておけば、公平性のテストと演出づくりが互いを邪魔しない。

偏りのテストと検証方法

乱数ロジックを実装したら、数万回試行させて各結果の出現回数を集計し、想定した確率に近いかを確認するテストを書く。特にシャッフル処理は「配列の先頭に偏りが出る」実装ミス(不適切なFisher-Yatesの変種)が起きやすく、統計的な検証がないと気づきにくい。

触ってみる2つのシャッフル実装を数千回試行して、ヒートマップを見比べてください

理論上は全マス20.0%になるはず。試行して確かめる

正しいFisher-Yatesは、回せば回すほど全マスが20%へ均されていく。一方ミス実装は、何万回試しても特定のマスが濃いまま動かない。数回シャッフルを眺めただけでは、この偏りはまず見抜けない。だからこそ数万回まわして数える検証が要る。

では何回試せば「偏りあり」と言えるのか。少ない試行では、公平な乱数でもたまたま大きくズレる。だから観測値が「公平ならこの範囲に収まるはず」という区間(およそ ±2σ)を外れて初めて、偏りと判断できる。必要な試行数は、検出したいズレの大きさで決まる。

触ってみるコインの偏りを変えて、観測の折れ線が公平の帯(ファネル)から何回目で外れるか見てください
約 984 回で帯の外へ

帯(ファネル)は、公平なコインなら95%の確率でこの中に収まるという範囲。試行の少ない左側では帯が広く、多少偏ったコインも中に紛れて見分けられない。右へ進むほど帯は締まり、観測の折れ線がやがて外へこぼれ出る。その地点の試行数が、その偏りを検出するのに要る回数だ。真の偏りが小さいほど、こぼれ出る地点は右へ遠のいていく。

乱数の使い道は公平な抽選だけではない。大量のランダムな試行から数値を推定する「モンテカルロ法」も、偏りのない乱数と大数の法則があってこそ成り立つ。

触ってみる点を打って、四分円に入った割合からπの推定値が真値に近づく様子を見てください

点を打つと 内側の割合×4 でπを推定する(真値 3.14159…)

数百点のうちは推定値が3.0〜3.3をふらつくが、数千点まで打つと3.14あたりに落ち着く。誤差が縮むペースは、おおよそ試行数の平方根に反比例する。精度を1桁上げたければ試行は100倍要る、ということだ。ここでも効くのは1回の結果ではなく大量の集計。ただし乱数の質が低ければ、いくら打っても真値からずれたまま止まる。

再現性が必要な場面での設計

共有リンクで同じ結果を再現したい機能(例: このURLを開けば同じチーム分け結果になる)では、シード値をURLのクエリパラメータに含める設計にする。これにより「乱数だけど再現できる」という一見矛盾した要件を満たせる。

ただしランダムなシードやIDを「ぶつからない前提」で使うのは危険だ。値の取りうる範囲が広くても、発行する個数が増えると重複(衝突)は思ったより早く起きる。誕生日のパラドックスとして知られる現象で、シードやトークンの設計では必ず意識する。

触ってみる空間サイズと発行する個数を変えて、衝突が起きる確率を確かめてください
理論上の衝突確率50.7%
実測(3,000回試行)
空間 365通り

全体の√くらいの個数で衝突は50%を超える。IDやシードは「広そう」でも意外に早くぶつかる

365通りしかないなら、たった23個並べただけで衝突確率が50%を超える。ざっくりの目安は、空間サイズの平方根くらいの個数。短い乱数シードを共有リンクに載せるつもりなら、この「意外な早さ」を見込んで桁数を多めに取っておきたい。

最後に、確率設計は「1回の期待値」だけで語れないことも押さえておきたい。ドロップ率から平均回数を出せても、実際に必要な回数は人によって大きくばらつく。この「運の悪い側の裾」をどう扱うかが、天井(保証)などの設計判断につながる。

触ってみるドロップ率を変えて、期待回数と「実際に必要な回数」のズレを見てください
期待回数 33.350%: 2390%: 7699%: 152

期待回数「33.3回で1体」でも、半数の人は 23回、1割の人は 76回以上引く。これが天井(保証)を設ける理由

「期待回数◯回で1体」の◯回は、平均にすぎない。半分の人はそれより多く引き、運の悪い1割は倍近くまで伸びることもある。大事なのはカーブの右の裾、つまり不運な人がどこまで引かされるかで、天井や保証はそこを見てから決める。平均値だけを眺めていると、この裾をまるごと見落とす。

次はこれ