8
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

rand関数からメルセンヌ・ツイスターに乗り換えた話

Last updated at Posted at 2019-06-26

この記事は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

8
9
8

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
8
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?