この記事はC++を始めたばかりの方を対象としています。
プログラムで日々のご飯を食べている方や、スーパーなプログラマーの方々は好きなプリキュアについて語っていてください。
はじめに
初心者向けの本やサイトでは、乱数を取得する際にrand関数を使用しているのを見かけます。
実際に自分が学んだ際もまずはそれで覚えました。
rand関数で範囲を指定して値を取得する方法として
rand() % n;
と書くことで0~n-1までの値を得ることが可能です。
rand関数のだめなところ
しかしrand関数は0~32767の値までしか(環境にもよりますが)取得できず、% n
といった範囲の指定もわかりにくいものとなっています。
少し逸れますが、Unityであれば
Random.Range(0, n);
とわかりやすく取得できます。
比較すると一目瞭然ではないでしょうか。
rand() % n;
Random.Range(0, n);
このプログラムには問題がある
最大値(RAND_MAX)が32767の為、nの値が大きくなるほど取得する値に偏りがでてしまいます。
以下のプログラムは、rand関数で0~9999までの値を1億回繰り返して取得し、1000毎に取得件数を出したものです。
#include <iostream>
#include <stdlib.h>
#include <time.h>
int main()
{
// 取得した乱数の1000の位を基準に件数を保存する配列
int randRes[10] = {};
// 乱数初期化
srand((unsigned)time(NULL));
// 1億回乱数取得する
for (int i = 0; i < 100000000; ++i)
{
// 0~32767 % 10000
int tmp = rand() % 10000;
// 1000で割ることで1000の位がいくつか取得する
int index = tmp / 1000;
// 該当する値を増やす
randRes[index]++;
}
// 件数出力
for (int i = 0; i < 10; ++i)
{
std::cout << "取得した値が"<<i * 1000 << "~" << (i + 1) * 1000 << "の件数:\t" << randRes[i] << " \n";
}
}
実行すると、0~3000までの値が多くなっていることが分かります。
出力
取得した値が0~1000の件数: 12208837
取得した値が1000~2000の件数: 12209757
取得した値が2000~3000の件数: 11499748
取得した値が3000~4000の件数: 9157498
取得した値が4000~5000の件数: 9147998
取得した値が5000~6000の件数: 9157796
取得した値が6000~7000の件数: 9155344
取得した値が7000~8000の件数: 9153517
取得した値が8000~9000の件数: 9155966
取得した値が9000~10000の件数: 9153539
そうだ!メルセンヌ・ツイスター!
この偏りを無くす方法として、C++11から追加された乱数ライブラリ「std::random」を使用します。
メルセンヌ・ツイスター(Mersenne twister)については一番下に参考サイトを載せてあります。
以下のプログラムは上の処理をstd::randomで再現したものです。
#include <iostream>
#include <random>
int main()
{
// 取得した乱数の1000の位を基準に件数を保存する配列
int randRes[10] = {};
std::random_device rnd; // 非決定的な乱数生成
std::mt19937 mt(rnd()); // 擬似乱数生成(メルセンヌ・ツイスター)。上で生成した乱数をシード値として扱う
std::uniform_int_distribution<int> intRand(0, 9999);// 一様分布生成器
// 1億回乱数取得する
for (int i = 0; i < 100000000; ++i)
{
int tmp = intRand(mt);
// 1000で割ることで1000の位がいくつか取得する
int index = tmp / 1000;
// 該当する値を増やす
randRes[index]++;
}
// 件数出力
for (int i = 0; i < 10; ++i)
{
std::cout << "取得した値が" << i * 1000 << "~" << (i + 1) * 1000 << "の件数:\t" << randRes[i] << " \n";
}
}
実行すると、ほぼ均等に乱数が生成されているのが分かります。
出力
取得した値が0~1000の件数: 9994603
取得した値が1000~2000の件数: 10005293
取得した値が2000~3000の件数: 10005488
取得した値が3000~4000の件数: 10001651
取得した値が4000~5000の件数: 9994822
取得した値が5000~6000の件数: 9998745
取得した値が6000~7000の件数: 9998542
取得した値が7000~8000の件数: 9999179
取得した値が8000~9000の件数: 10004467
取得した値が9000~10000の件数: 9997210
まとめ
ということでメルセンヌ・ツイスターを使おう!
おわり!
参考にしたサイト
メルセンヌ・ツイスタ wikipedia
https://ja.wikipedia.org/wiki/%E3%83%A1%E3%83%AB%E3%82%BB%E3%83%B3%E3%83%8C%E3%83%BB%E3%83%84%E3%82%A4%E3%82%B9%E3%82%BF
C言語による乱数生成
http://www.sat.t.u-tokyo.ac.jp/~omi/random_variables_generation.html