C++11 random 覚え書き
C++11以降では<random>ヘッダで良質な擬似乱数を得ることができる。
#include <random>
外部からの乱数
外部から乱数を得るにはrandom_deviceを使う。
#include <random> #include <iostream> int main() { std::random_device rand_dev; std::cout << rand_dev() << std::endl; std::cout << rand_dev() << std::endl; return 0; }
上のように、外部からの乱数源は関数として呼び出すことで乱数を生成する。
このプログラムは実行するごとに異なる値を出力する。
擬似乱数
擬似乱数は、シード値を放り込むと乱数っぽい数列を吐き出すプログラム。
生成方法が何種類かあるが、mt19937だけ把握していればよい。
#include <random> #include <iostream> int main() { std::mt19937 rand_src(12345); std::cout << rand_src() << std::endl; std::cout << rand_src() << std::endl; return 0; }
上のように、擬似乱数生成器は関数として呼び出すことで擬似乱数を生成する。
このプログラムは異なる整数を2つ出力するが、プログラムの実行ごとに同じ整数を出力する。
ソース中の12345がシード値である。この数値を変更すると、異なる整数を出力するようになる。
乱数の加工
上で生成した乱数は32bit一様乱数等なので、必要な分布が出るように加工する。
#include <random> #include <iostream> int main() { std::mt19937 rand_src(12345); std::uniform_int_distribution<int> rand_dist(0, 99); std::cout << rand_dist(rand_src) << std::endl; std::cout << rand_dist(rand_src) << std::endl; return 0; }
上のように、分布を関数として使うときは、引数に乱数源への参照を渡す。内部で乱数源が(必要に応じて複数回)呼ばれている。
上のソースコードでは、MTで生成される擬似乱数列をもとに、一様分布に従う0以上99以下の整数を2つ生成している。
よく使う分布:
- 離散一様分布 uniform_int_distribution
- テンプレート引数 : 戻り値の型(int等)
- コンストラクタの引数 : a以上b以下の乱数を生成
- 連続一様分布 uniform_real_distribution
- テンプレート引数 : 戻り値の型(double等)
- コンストラクタの引数 : a以上b未満の乱数を生成
- 正規分布 normal_distribution
暗号論的擬似乱数
たぶんこのライブラリには無い。
分布と擬似乱数生成器を合体させる
分布に毎回擬似乱数生成器をつけるのは面倒であるから、<functional>ヘッダにあるbindとrefを使って以下のようにする。
#include <random> #include <iostream> #include <functional> int main() { std::mt19937 rand_src(12345); std::uniform_int_distribution<int> rand_dist(0, 9); auto rand_dist_bound = std::bind(rand_dist, std::ref(rand_src)); std::cout << rand_dist_bound() << std::endl; std::cout << rand_dist_bound() << std::endl; return 0; }
refを使わずに、
auto rand_dist_bound = std::bind(rand_dist, rand_src);
としても一応動作するが、これは擬似乱数生成器のコピーを作成してしまうので意味が変わる。同じ擬似乱数生成器を複数の分布で使う場合は注意が必要(通常、refをつけるほうが好ましい)
これは以下のようにしても同じことである:
#include <random> #include <iostream> #include <functional> int main() { std::mt19937 rand_src(12345); auto rand_dist_bound = std::bind( std::uniform_int_distribution<int>(0, 9), std::ref(rand_src)); std::cout << rand_dist_bound() << std::endl; std::cout << rand_dist_bound() << std::endl; return 0; }
さらに、この擬似乱数生成器はこの分布でしか使わないというのであれば、以下のようにもできる(この場合はrefは使わない):
#include <random> #include <iostream> #include <functional> int main() { auto rand_dist_bound = std::bind( std::uniform_int_distribution<int>(0, 9), std::mt19937(12345)); std::cout << rand_dist_bound() << std::endl; std::cout << rand_dist_bound() << std::endl; return 0; }
シードの決め方
シードの決め方は場合による。大雑把に言うと次の2種類:
- 外部からの乱数に基づいて決める
- 決め打ち
ただし、以下のことに注意が必要だ:
外部からの乱数に基づいてシードを決める
外部からの乱数に基づいてシードを決めるには、単に以下のようにすればよい:
#include <random> #include <iostream> int main() { std::random_device rand_dev; std::mt19937 rand_src(rand_dev()); std::cout << rand_src() << std::endl; std::cout << rand_src() << std::endl; return 0; }
このようにすると、毎回シードが変わるので、実行するごとに異なる整数が表示される。
外部からの乱数源を1回しか使っていないので、以下のようにもできる:
#include <random> #include <iostream> int main() { std::mt19937 rand_src(std::random_device{}()); std::cout << rand_src() << std::endl; std::cout << rand_src() << std::endl; return 0; }
コマンドライン引数に基づいてシードを決める
例えば、コマンドライン引数の1番目がある場合はそれをシードとして使うようにするには:
#include <random> #include <iostream> #include <string> int main(int argc, char *argv[]) { int seed = 12345; if(argc > 1) { seed = std::stoi(argv[1]); } std::mt19937 rand_src(seed); std::cout << rand_src() << std::endl; std::cout << rand_src() << std::endl; return 0; }
また、コマンドライン引数を与えない場合のデフォルトは、外部からの乱数でもよいかもしれない:
#include <random> #include <iostream> #include <string> int main(int argc, char *argv[]) { int seed; if(argc > 1) { seed = std::stoi(argv[1]); } else { seed = std::random_device{}(); } std::mt19937 rand_src(seed); std::cout << rand_src() << std::endl; std::cout << rand_src() << std::endl; return 0; }
サンプルコード
POSIX usleep(3)を使って、1秒間あたり平均5個のランダムな(ポアソン過程に基づく)イベントを発生させる (十分たくさんの粒子があるときの放射線の発生とかがこれ)
#include <random> #include <iostream> #include <string> #include <functional> #include <unistd.h> int main(int argc, char *argv[]) { int seed; if(argc > 1) { seed = std::stoi(argv[1]); } else { seed = std::random_device{}(); } std::mt19937 rand_src(seed); auto rand = std::bind( std::exponential_distribution<double>(5.0 / 1000000.0), std::ref(rand_src)); while(true) { std::cout << "a" << std::flush; usleep(rand()); } std::cout << rand_src() << std::endl; std::cout << rand_src() << std::endl; return 0; }