太線=シード42の列。薄い2本はすぐ隣のシード(41と43)。1つずらすだけで形は無関係に飛ぶのに、同じシードに戻せば毎回この太線が再現される
太線を1本の乱数列だと思ってほしい。シードを1つ動かすだけで、すぐ隣に薄く残る前後のシードとは似ても似つかない形へ飛ぶ。なのに同じシードへ戻せば、寸分違わず同じ線が返ってくる。この「決まっているのにランダムに見える」性質こそが、疑似乱数を実用にしている。
PRNGの仕組みとシード値
コンピュータの「乱数」は厳密には決まった計算式で作られる疑似乱数(PRNG)で、同じ「シード値」から始めれば毎回同じ数列が再現される。Math.random()はシードを指定できないため、再現性が必要な場面(同じ条件で結果を検証したい、共有リンクで同じ結果を再現したい)では、シード指定可能な軽量PRNG(mulberry32等)を自前で用意する。
ただし「シードを指定できる」ことと「質が高い」ことは別問題だ。出力を1つずつ見ると均等に散らばっていても、隣り合う値の関係に隠れた規則性が残る実装もある。乱数の質は、1次元の分布だけでなく多次元の並びで検証する。
各点=(uₙ, uₙ₊₁)。斜めの線は約 5 本 — 見た目は均等でも隣接値に構造が残る
値そのものは0〜1にきれいに散らばっているのに、(uₙ, uₙ₊₁)を打つと斜めの縞が浮かび上がる低品質な線形合同法(LCG)。次の値が前の値からほぼ計算できてしまうサインだ。良質なPRNGなら縞は消え、構造のない一様な雲になる。1次元で均等に見えても、それだけでは質は保証できない。
一様な乱数は、足し合わせるだけで別の分布を作り出す土台にもなる。偏りのない乱数源が1つあれば、そこから狙った形の分布を組み立てられる。
1個なら1〜6が均等(一様分布)
サイコロ1個の目は1〜6が平らに並ぶだけ。それを数個足すと、中央がこんもり盛り上がった釣鐘型(正規分布)に変わる。これが中心極限定理で、一様な乱数さえ手元にあれば正規分布や「自然っぽい揺らぎ」を組み立てられる。乱数演出でよく使う土台だ。
公平性の担保(おみくじ・ルーレット・チーム分け)
「公平」に見えるかどうかは、実際の確率だけでなく見た目の演出にも左右される。ルーレットは回転速度を毎回ランダムに変え、同じ位置で止まって見えないようにする。おみくじは結果の出現確率をテーブルで明示的に管理し、大吉のような希少な結果には低い確率を明示的に割り当てる。チーム分けはランダムに見えても、実際には人数の偏りが出ないよう「まず人数を均等に割ってから中身をシャッフルする」実装にする。
1回引いただけでは理論値から派手に外れる。ところが100回単位で引き足すと、実測の濃いバーが薄い理論バーへぴたりと寄っていく。確率テーブルを持つというのは、この寄っていく先の「理論値」を自分の手であらかじめ決めておくこと。数字を設計する作業そのものだ。
ルーレットのような演出付きの抽選では、「結果を決めるロジック」と「見せ方の演出」を分けて考えるのが要点だ。結果は先に一様乱数で公平に決めてしまい、回転量や速度といった演出だけを毎回ランダムに変える。こうすれば派手な演出と厳密な公平性を両立できる。
累計 0回 — 演出がどれだけ派手でも各区画の出現はほぼ均等(公平)
何周回るかも、止まるまでの時間も毎回バラバラ。それでも累計を数えると、各区画はほぼ同じ回数で出ている。なぜか。結果を先に一様乱数で決め、あとから派手な演出だけを乗せているからだ。こう切り分けておけば、公平性のテストと演出づくりが互いを邪魔しない。
偏りのテストと検証方法
乱数ロジックを実装したら、数万回試行させて各結果の出現回数を集計し、想定した確率に近いかを確認するテストを書く。特にシャッフル処理は「配列の先頭に偏りが出る」実装ミス(不適切なFisher-Yatesの変種)が起きやすく、統計的な検証がないと気づきにくい。
理論上は全マス20.0%になるはず。試行して確かめる
正しいFisher-Yatesは、回せば回すほど全マスが20%へ均されていく。一方ミス実装は、何万回試しても特定のマスが濃いまま動かない。数回シャッフルを眺めただけでは、この偏りはまず見抜けない。だからこそ数万回まわして数える検証が要る。
では何回試せば「偏りあり」と言えるのか。少ない試行では、公平な乱数でもたまたま大きくズレる。だから観測値が「公平ならこの範囲に収まるはず」という区間(およそ ±2σ)を外れて初めて、偏りと判断できる。必要な試行数は、検出したいズレの大きさで決まる。
帯(ファネル)は、公平なコインなら95%の確率でこの中に収まるという範囲。試行の少ない左側では帯が広く、多少偏ったコインも中に紛れて見分けられない。右へ進むほど帯は締まり、観測の折れ線がやがて外へこぼれ出る。その地点の試行数が、その偏りを検出するのに要る回数だ。真の偏りが小さいほど、こぼれ出る地点は右へ遠のいていく。
乱数の使い道は公平な抽選だけではない。大量のランダムな試行から数値を推定する「モンテカルロ法」も、偏りのない乱数と大数の法則があってこそ成り立つ。
点を打つと 内側の割合×4 でπを推定する(真値 3.14159…)
数百点のうちは推定値が3.0〜3.3をふらつくが、数千点まで打つと3.14あたりに落ち着く。誤差が縮むペースは、おおよそ試行数の平方根に反比例する。精度を1桁上げたければ試行は100倍要る、ということだ。ここでも効くのは1回の結果ではなく大量の集計。ただし乱数の質が低ければ、いくら打っても真値からずれたまま止まる。
再現性が必要な場面での設計
共有リンクで同じ結果を再現したい機能(例: このURLを開けば同じチーム分け結果になる)では、シード値をURLのクエリパラメータに含める設計にする。これにより「乱数だけど再現できる」という一見矛盾した要件を満たせる。
ただしランダムなシードやIDを「ぶつからない前提」で使うのは危険だ。値の取りうる範囲が広くても、発行する個数が増えると重複(衝突)は思ったより早く起きる。誕生日のパラドックスとして知られる現象で、シードやトークンの設計では必ず意識する。
全体の√くらいの個数で衝突は50%を超える。IDやシードは「広そう」でも意外に早くぶつかる
365通りしかないなら、たった23個並べただけで衝突確率が50%を超える。ざっくりの目安は、空間サイズの平方根くらいの個数。短い乱数シードを共有リンクに載せるつもりなら、この「意外な早さ」を見込んで桁数を多めに取っておきたい。
最後に、確率設計は「1回の期待値」だけで語れないことも押さえておきたい。ドロップ率から平均回数を出せても、実際に必要な回数は人によって大きくばらつく。この「運の悪い側の裾」をどう扱うかが、天井(保証)などの設計判断につながる。
期待回数「33.3回で1体」でも、半数の人は 23回、1割の人は 76回以上引く。これが天井(保証)を設ける理由
「期待回数◯回で1体」の◯回は、平均にすぎない。半分の人はそれより多く引き、運の悪い1割は倍近くまで伸びることもある。大事なのはカーブの右の裾、つまり不運な人がどこまで引かされるかで、天井や保証はそこを見てから決める。平均値だけを眺めていると、この裾をまるごと見落とす。