C++11で使える乱数 – メルセンヌ・ツイスター法 (`・ω・´)シャキーン

====2017/7/29追記====

このページが意外と参照されているようなので追記しました
メルセンヌ・ツイスターについて

========

まだ2回目の更新でしょうか(・ω・;)
アカウントを作ったは良いものの、
諸事情により更新する時間が確保できず
(ブログの存在をしばらく忘れていたため)
こんなになってしまいました(汗)

ただ一つ分かったことがあります。
有名ブログとかに登録しておけば、
何も更新されていなくてもGoogle検索でヒットするようになるということです!

みなさんも、ブログ作ろうと思い立つ前にサイトだけ作っておくと幸せになれるかも。

今回はC++11の乱数について紹介を。
参考:
ゲーム開発者のためのC++11/C++14(SlideShare)
cppreference.com – Pseudo-random number generation

参考スライドはC++11全般について書かれていますが、
簡単かつ恩恵を受けられそうなものの代表として、
まずは参考スライド同様に乱数についてからにしようかと。
(あと、スライドの例そのままだとエラー吐かれたんですよね…)

—-

C/C++標準ライブラリの乱数については、
全然乱数でないことで有名ですね。
だいたいの用途には事足りるのかもしれませんが、
奇数より偶数のほうが出現しやすいとか、気持ち悪い仕様であります。

そんな需要に応えてか、C++11では、今までの標準rand()関数に加えて、
しっかりした乱数生成クラスが採用されたました。

古い仕様の関数を完全に置き換えてくれてもよかった気がしなくもないのですが、
新しいクラスとして追加です。
(クラスのため、C言語からは呼べないんですよね。大学の研究室=乱数が必要なところ とかでは未だに純粋Cを使ってたりすると思うんですが。)

たとえば、1から100までのランダムな整数がほしかったら

int main(){
  std::random_device rnddev;
  std::mt19937 mt(rnddev());

  std::uniform_real_distribution<double> rnd(1.0, 10.0);

  for(int i=0; i<10; i++){
    std::cout << rnd(mt) << std::endl;
  }

  return 0;
};

となります。

乱数生成のアルゴリズムは世界にたくさん存在しているが、上のコードは『メルセンヌ・ツイスター法』とかいうアルゴリズムを使っています。
決して 名 前 で 選 ん だ なんてことはありません(`・ω・´)
高速かつ十分ランダムな実用的乱数生成方法として知られているものです。
(うちの研究室でも使っています。)

std::uniform_int_distribution<int> rnd(1, 10);

の部分ですが、
-20から40までの整数がほしければ

std::uniform_int_distribution<int> rnd(-20, 40);

とすればいいですし、
実数(double精度)でほしければ

std::uniform_real_distribution<double> rnd(1.0, 10.0);

とすればOKです。

等確率で値が出るのが普通の乱数(一様分布)ですが、
たとえば正規分布状に値が出現してほしい時というのもあります。
C++11では、
・一様分布
・ベルヌーイ分布
・ポアソン分布
・正規分布
・サンプリング分布
が、標準で使えるようです。
平均が0、標準偏差が1の正規分布なら、

double mu = 0.0;
double sigma = 1.0;
std::normal_distribution<double> rnd(mu, sigma);

こんな感じ。
自分の場合、正規分布以外はあまり使う機会なさそうです…。

昔のsrand((unsigned)time(NULL));
に相当するのが

std::random_device rnddev;
std::mt19937 mt(rnddev());

です。
random_device というのが、時刻とか機械固有の番号とかを調べて乱数を作るためのクラスらしいです。
処理時間を気にしないのであればこのクラスだけを使ってもいいのだとか。
ただし遅いので、普通はメルセンヌ・ツイスターなどのアルゴリズムを使います。

それと、ランダム性をさらに上げたい場合には、
random_deviceでいくつか乱数を生成して、それら全部を考慮するみたいなことをするらしいです。
あと、本当はメルセンヌ・ツイスター法を使うためのクラス mt19937 も、簡単に使えるようにてきとーに(適切に)設定されたパラメータを使っているらしく、もっとカスタマイズ可能らしいです。それ以上はわかりません(・ω・)

C++に慣れていない人には、
rnd(mt);
の部分が不思議かもしれませんが、まぁこういうものです。
「rnd」って変数名なのに、関数みたいに使っていますからね。

よく、
「0から9までの数を得るためには、 rand()%10 とすればいい」みたいな話もあります。
昔のrand()関数を使って手軽に処理したい場合にはこれでもいいですが、ランダム性は低いです。
rand()関数の仕様がそもそも悪いことで有名ですし、
そうでなくても、割り算の余りを使うというのは安直すぎます。
仮に、0から9まで等確率で出現する関数f()があったとします。 f()%7は、3から6よりも0,1,2になりやすいですよね。スケールは違いますが、そういう状況は起きます。
っていうか、なんで昔のC言語の規約であんな中途半端な乱数が認められたのか…。

広告

C++11で使える乱数 – メルセンヌ・ツイスター法 (`・ω・´)シャキーン」への1件のフィードバック

  1. ピンバック: メルセンヌ・ツイスターについて | わくわくとおーぼえ

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト /  変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト /  変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト /  変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト /  変更 )

%s と連携中