📌

C++ で乱数を扱う

2023/03/23に公開

はじめに

C++ で乱数を扱う方法を紹介します.

疑似乱数生成

random ヘッダで定義されている疑似乱数生成器を用いて疑似乱数を生成することができます.
特に理由がなければ生成器として std::mt19937 を使用すればいいです.

// インスタンス生成
std::mt19937 engine;

// シードを指定してインスタンス生成
// 上記のようにシードを指定しない場合は, 毎回デフォルト値が指定される
std::mt19937 engine(seed);

// 疑似乱数生成
// engine は関数オブジェクト
engine();

疑似乱数であるため, 等しいシード値に対して等しい乱数列を出力します.

真の乱数生成

プログラムの実行毎 (もしくはインスタンスの生成毎) に異なる乱数列を生成したいことがあると思います.
std::random_device はハードウェアで生じたノイズを用いることで予測のできない真の乱数列を生成します.

// インスタン生成
std::random_device engine;

// 真の乱数生成
engine();

真の乱数生成は疑似乱数生成に比べて処理速度が遅いので, 疑似乱数生成器のシードのみを生成するために用います.
シードが真の乱数に基づいて生成されるので, 実行ごとに異なる疑似乱数列が得られます.

std::random_device seed_gen;
std::mt19937 engine(seed_gen());

engine();
疑似乱数と真の乱数の処理速度比較

Apple M2 で実行.

T = 100'000'000

std::mt19937 engine;
for (int i = 0; i < T; ++i)
    engine();
// >>> 1734 ms

std::random_device engine;
for (int i = 0; i < T; ++i)
    engine();
// >>> 13390 ms

乱数の加工

確率分布に基づいた乱数を生成することもできます.

std::random_device seed_gen;
std::mt19937 engine(seed_gen());

// 離散一様分布 U(1, 6)
// テンプレートパラメータを int にしているため, 生成される乱数は int 型整数
std::uniform_int_distribution<int> dist(1, 6);

// 分布に基づいて乱数生成
dist(engine);

// 標準正規分布 N(0, 1)
std::normal_distribution<double> dist(0., 1.);

dist(engine);
分布に基づいて生成されることの確認

正規分布 \mathcal{N}(0, 10) に基づいて 10 万個の乱数を生成し, それぞれの区間に含まれる数をカウント.

std::random_device seed_gen;
std::mt19937 engine(seed_gen());

std::normal_distribution<double> dist(0., 10.);
std::map<int, int> count;
int T = 100'000;
for (int i = 0; i < T; ++i) {
    int x = dist(engine);
    ++count[x];
}

int unit = 100;
for (auto& [key, val] : count) {
    if (val < unit) continue;
    std::cout << "[" << std::setw(3) << key << ", " << std::setw(3) << key + 1 << "): ";
    for (int i = 0; i < val / unit; ++i) {
        std::cout << "#";
    }
    std::cout << std::endl;
}

カウント 100 ごとに # をプロット (100 に満たない区間は除外).

[-27, -26): #
[-26, -25): #
[-25, -24): #
[-24, -23): ##
[-23, -22): ##
[-22, -21): ###
[-21, -20): ###
[-20, -19): ####
[-19, -18): ######
[-18, -17): #######
[-17, -16): ########
[-16, -15): #########
[-15, -14): ###########
[-14, -13): ##############
[-13, -12): ################
[-12, -11): ##################
[-11, -10): #####################
[-10,  -9): ######################
[ -9,  -8): ########################
[ -8,  -7): ###########################
[ -7,  -6): ##############################
[ -6,  -5): ################################
[ -5,  -4): ##################################
[ -4,  -3): ##################################
[ -3,  -2): #####################################
[ -2,  -1): ######################################
[ -1,   0): #######################################
[  0,   1): ###############################################################################
[  1,   2): #######################################
[  2,   3): ######################################
[  3,   4): #####################################
[  4,   5): ###################################
[  5,   6): ###################################
[  6,   7): ################################
[  7,   8): ##############################
[  8,   9): #########################
[  9,  10): #########################
[ 10,  11): #######################
[ 11,  12): ####################
[ 12,  13): ##################
[ 13,  14): ################
[ 14,  15): #############
[ 15,  16): ###########
[ 16,  17): ##########
[ 17,  18): ########
[ 18,  19): #######
[ 19,  20): ######
[ 20,  21): ####
[ 21,  22): ###
[ 22,  23): ###
[ 23,  24): ##
[ 24,  25): ##
[ 25,  26): #
[ 26,  27): #

Discussion