指定した範囲の乱数を作る際の罠(前編)

アルゴリズム
スポンサーリンク

はじめに

みなさん。「指定した範囲で乱数を作りたい」と思ったとき、普段どうしていますか?

今日は、そんな乱数生成の小ネタをご紹介します。

サイコロを作ってみよう

整数の乱数を使う場合

サイコロを作りたいと思います。サイコロの目は1~6ですね。

ここでお使いのプログラミング言語に整数型の乱数生成器が用意されており、この乱数生成器は「4バイト=32ビットの一様分布する整数値」が出力され、random32()と関数を呼び出せば、出力されるものとします。C言語の乱数 rand()と同様の関数だと考えてください。

さて、どういうコードを書きますか?

おそらく、一番シンプルな次のコードです。

サイコロの目 = random32() % 6 + 1

(% は、6で割った余りの数とします)

このコードは、

  1. random32() % 6」で 0〜5 の数字を作る
  2. そこに1を足して、1〜6の乱数を作る

という流れです。

実数の乱数を使う場合

今度は、実数(浮動小数点数)の乱数生成器を使う場合を考えます。

実数の乱数生成器は64ビットの double型の実数で、0以上1未満([0, 1))の乱数を返してくれるものとします。関数名は randomf() 、JavaScriptの Math.random() だと考えてください。

この場合も、どういうコードを書きますか?

最も簡単なコードは、次のようなコードです。

サイコロの目 = floor(6 * randomf()) + 1

(floor() は、床関数を指します)

こちらのコードは、

  1. 6 * randomf()」で[0, 6)の乱数を作成して床関数で0~5の整数値へ変換
  2. そこに1を足して、1〜6の乱数を作る

という流れです。

これで本当に大丈夫?

「とりあえず手っ取り早く乱数がほしい!」という場合は、今までの方法でも十分です。

実はこの方法、本当に正確な乱数がほしい場合は問題があるんです。

何が問題なんでしょう?

乱数生成の落とし穴

整数乱数の場合

分かりやすくするため、もっと単純な乱数を考えます。

2ビットの乱数生成器(0~3までの4パターンの数字を出力する)random4()が用意されており、ここから 0,1,2 の 3 パターンの数字を出力する乱数を作り出したいと思います。

この乱数生成器の出力は:

乱数値 0 1 2 3
確率 1/4 1/4 1/4 1/4
y = random4() mod 3

すると各乱数値が3で割った余り(y)は:

乱数値 0 1 2 3
y 0 1 2 0

つまり出力される 0〜2 の確率は

y 0 1 2
確率 2/4 1/4 1/4

実数乱数の場合

今度は実数の乱数で考えましょう。

実数なら問題ないと考えるかもしれませんが、実数の乱数を作成するための浮動小数点数に「表現できる値の限界」があるため同様の問題が起こりえます。

double型の乱数(仮数部52ビット)なら2の52乗(約9000兆)ものパターンがあるので、現実的には問題は考えにくいですが、もしこれが、2ビット整数の乱数生成器の話と同じように仮数部が2ビットしかない場合はどういった結果になるか考えていきます。

実数の乱数生成器 randomf4() は 0, 0.25, 0.5, 0.75 の4つの値しか返さないとします。

乱数値 0 0.25 0.5 0.75
確率 1/4 1/4 1/4 1/4
y = randomf4() * 3
z = floor(y)

とすると…

乱数値 0 0.25 0.5 0.75
y 0 0.75 1.5 2.25
z 0 0 1 2
z 0 1 2
確率 2/4 1/4 1/4

おわりに

「じゃあ、どうすればいいの?」と思いますよね。

日常的に使う分には最初の方法で十分です。でも、「どんな乱数生成器でもしっかり偏りのない乱数を作りたい!」という場合は、この偏りを避ける工夫が必要になります。

その方法は後編でご紹介します!

コメント

タイトルとURLをコピーしました