乱数生成関数を使っても「乱れてくれない」のはなぜか? — シード値の罠と擬似乱数の正体
はじめに
「アプリを起動するたびに、ランダムなはずの挙動がいつも同じ……」
「テスト実行のたびに、全く同じ数値のリストが生成される……」
プログラミングを始めたばかりの頃などに、こんな経験はないでしょうか?
「ランダム」という言葉のイメージに反して、コンピュータが吐き出す数字がちっとも乱れてくれない。
そこには、コンピュータが抱える 「計算でデタラメを作る」という構造的な矛盾 が隠れています。
1. コンピュータは「計算」しかできない
まず、コンピュータは真の意味で「デタラメ(真の乱数)」を生み出すことが苦手です。
私たちが普段使っている乱数生成関数は、正確には 「擬似乱数生成器(PRNG)」 と呼ばれます。
これは、特定の数式に沿って計算を行い、「一見ランダムに見える数列」を高速に生成する仕組みです。
ここで重要になるのが、今回の主役である 「シード値(種)」 です。
2. 「シード値」は乱数の設計図
擬似乱数は、最初に与えられた「シード値」という数値を入力として、次の数値を計算します。
イメージで言うと、
乱数生成器は「分厚い辞書」のようなものです。
シード値: 辞書の「何ページ目から読み始めるか」を指定する番号
乱数生成: そのページから順番に単語を読み上げていく作業
もし、毎回「100ページ目(シード値:100)」から読み始めれば、
そこから続く単語のリスト(乱数列)は、いつ、誰が実行しても全く同じになります。
これが、乱数が「乱れてくれない」最大の理由です。
3. JavaScriptで見る「乱れない」罠
JavaScriptの Math.random() は、実行時にブラウザやランタイムが自動でシード値を設定してくれるため、
通常は意識せずとも「乱れて」くれます。
しかし、 「再現性が欲しい(シード値を固定したい)」場合や、
「低レイヤな言語でシードを自分で管理する場合」 を想定して、シード値の重要性をコードで確認してみましょう。
パターンA:シード値が固定されている(再現性の罠)
独自にシード値を受け取る乱数生成関数(線形合同法など)を例にします。
// 簡易的な擬似乱数生成器(シード値を受け取るタイプ)
function seededRandom(seed) {
return function() {
seed = (seed * 1664525 + 1013904223) % 4294967296;
return seed / 4294967296;
};
}
// 毎回同じシード値「42」で初期化すると...
const rollDice = seededRandom(42);
console.log(rollDice()); // 0.32... (毎回同じ)
console.log(rollDice()); // 0.78... (毎回同じ)
このコードを何度実行しても、最初に出る数字は必ず同じになります。
これが「乱れない」状態の正体です。
パターンB:ループの中で初期化している(初心者の罠)
「実行するたびに違うシードを使おう!」として、現在時刻をシードにする際にやりがちなミスです。
// ダメな例:高速なループ内で「秒」単位のシードを使う
for (let i = 0; i < 5; i++) {
// 1秒未満でループが回るため、Date.now() が同じ値を返し続ける
const seed = Math.floor(Date.now() / 1000);
const randomFunc = seededRandom(seed);
console.log(`回数 ${i}: ${randomFunc()}`);
// すべて同じ結果になってしまう!
}
現代のコンピュータは爆速です。1秒間に数百万回のループを回すため、
秒単位のシードでは「すべて同じシード」として扱われてしまいます。
4. 正しく「乱す」ための処方箋
現代のJavaScript開発において、乱数を適切に扱うためのポイントは以下の通りです。
通常はライブラリに任せる: Math.random() は内部でOSレベルのシードを使用するため、
基本的にはこれを使えば「乱れない」問題は起きません。
シードを自分で決めるなら「開始時に1回だけ」: seededRandom(Date.now()) のように、
プログラムの初期化時に一度だけシードを植え、あとはそのインスタンスを使い回しましょう。
セキュリティが重要な場合: 暗号やトークン生成には crypto.getRandomValues() を使いましょう。
これは「暗号学的疑似乱数生成器(CSPRNG)」と呼ばれ、予測が極めて困難な仕組みになっています。
まとめ
乱数が「乱れない」のは、コンピュータがあなたの命令を忠実に守り、
同じ設計図(シード)から同じ数列を計算しているからです。
乱数は計算の結果です。
シード値が同じなら、結果も同じになる。
「いつ」「どの精度で」シードを植えるかが、エンジニアの腕の見せ所。
次に乱数が偏ったときは、ぜひ「今のシード値は何だろう?」と疑ってみてください。
最後に
BTMに少しでもご興味を持っていただけましたら、
ぜひBTM採用ページを覗いてみてください。
-
SNS
-
投稿日
-
カテゴリー
Tech Blog