0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

乱数生成関数を使っても「乱れてくれない」のはなぜか? — シード値の罠と擬似乱数の正体

0
Last updated at Posted at 2026-04-06

はじめに

「アプリを起動するたびに、ランダムなはずの挙動がいつも同じ……」
「テスト実行のたびに、全く同じ数値のリストが生成される……」

プログラミングを始めたばかりの頃などに、こんな経験はないでしょうか?

「ランダム」という言葉のイメージに反して、コンピュータが吐き出す数字がちっとも乱れてくれない。そこには、コンピュータが抱える 「計算でデタラメを作る」という構造的な矛盾 が隠れています。

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() は処理系(ブラウザやランタイム)ごとに実装された擬似乱数生成器を使用し、通常は実行ごとに異なるシードで初期化されるため、基本的にはこれを使えば「乱れない」問題は起きません。

シードを自分で決めるなら「開始時に1回だけ」: seededRandom(Date.now()) のように、プログラムの初期化時に一度だけシードを植え、あとはそのインスタンスを使い回しましょう。

セキュリティが重要な場合: 暗号やトークン生成には crypto.getRandomValues() を使いましょう。これは「暗号学的疑似乱数生成器(CSPRNG)」と呼ばれ、予測が極めて困難な仕組みになっています。Math.random() は予測可能なため、セキュリティ用途には使用してはいけません。

まとめ

乱数が「乱れない」のは、コンピュータがあなたの命令を忠実に守り、同じ設計図(シード)から同じ数列を計算しているからです。

乱数は計算の結果です。
シード値が同じなら、結果も同じになる。

「いつ」「どの精度で」シードを植えるかが、エンジニアの腕の見せ所。

次に乱数が偏ったときは、ぜひ「今のシード値は何だろう?」と疑ってみてください。

最後に

BTMに少しでもご興味を持っていただけましたら、
ぜひBTM採用ページを覗いてみてください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?