読者です 読者をやめる 読者になる 読者になる

稲枝の押入れ

いなえが適当なことを書いては、しまっておく場所

distributionで乱数の幅を決める時、負の値が指定できない

追記: 自分のアホでミスっていただけでした。結論から言うとresultをunsigned intにしているのに気付いていないままコードを書いていただけでした…お騒がせしました…
よって、この記事は情報が0に近い記事になってしまったわけですが、同じようなうっかりさんが居るかもしれないんのでこのまま残しておきます。

色々あって精度のそれなりな乱数を使いたいなって事でC++11から入ったメルセンヌ・ツイスタを使って乱数生成クラスを作ってたんですが、テストコードを走らせてる段階で何か少し変な挙動を発見。

コードの規模を小さくして検証してみると、どうもdistributionの動きが僕が思ってるのと違うっぽい?

以下0~100の範囲で乱数を100回生成させるようなコードなのですが、これはまあ思った通りに動く。 <コード>

#include<iostream>
#include<iomanip>
#include<random>


int main() {

    std::random_device rand_seed;//非決定論的乱数生成器
    std::mt19937 mt_obj(rand_seed());//メルセンヌ・ツイスタで乱数を生成する
    std::uniform_int_distribution<int>::param_type uniform_parameter(0, 100);//整形する幅を決める
    std::uniform_int_distribution<int> uniform(uniform_parameter);//生成された乱数を指定された幅の一様分布に整形する

    static int calc_num = 0;
    unsigned int result = uniform(mt_obj);


    for (int i = 0; i < 100; i++) {

        if (i != 0) {//初回は飛ばす
            result = uniform(mt_obj);
        }

        calc_num++;

        //表示の幅やレイアウトについて指定して出力している
        std::cout
            << std::setw(5) << std::left << calc_num
            << ":result = "
            << std::setw(5) << std::right << result
            << std::endl;
    }
    return 0;
}



<実行結果>

1    :result =    77
2    :result =    64
3    :result =    63
4    :result =    87
5    :result =    22
6    :result =     3
7    :result =    48
8    :result =    62
9    :result =     0
10   :result =    42
11   :result =    78
12   :result =    52
13   :result =    64
14   :result =    37
15   :result =    51
16   :result =    94
17   :result =    14
18   :result =    24
19   :result =    86
20   :result =    16
21   :result =    25
22   :result =    53
23   :result =    30
24   :result =    45
25   :result =    48
26   :result =    47
27   :result =    17
28   :result =    54
29   :result =    85
30   :result =    99
31   :result =    76
32   :result =    18
33   :result =   100
34   :result =    15
35   :result =    18
36   :result =    17
37   :result =    37
38   :result =    58
39   :result =    77
40   :result =    59
41   :result =    50
42   :result =     4
43   :result =    54
44   :result =    27
45   :result =    66
46   :result =    41
47   :result =    57
48   :result =    63
49   :result =    43
50   :result =    75
51   :result =    10
52   :result =    58
53   :result =    24
54   :result =     3
55   :result =     6
56   :result =    97
57   :result =    37
58   :result =     8
59   :result =    74
60   :result =    35
61   :result =    46
62   :result =    88
63   :result =    66
64   :result =    30
65   :result =    93
66   :result =    33
67   :result =    50
68   :result =    43
69   :result =    19
70   :result =    88
71   :result =    93
72   :result =    63
73   :result =    57
74   :result =    42
75   :result =    87
76   :result =    51
77   :result =    44
78   :result =    62
79   :result =    19
80   :result =    88
81   :result =    10
82   :result =    11
83   :result =    40
84   :result =    12
85   :result =    38
86   :result =    90
87   :result =    11
88   :result =    18
89   :result =    42
90   :result =    78
91   :result =    38
92   :result =     2
93   :result =    35
94   :result =    44
95   :result =    92
96   :result =    92
97   :result =    25
98   :result =     0
99   :result =    18
100  :result =    74

ところが、distributionの範囲にマイナスを含むようにすると動きが少しおかしくなる。

以下は-100~100の範囲で乱数を100回生成するコードだと… <コード>

#include<iostream>
#include<iomanip>
#include<random>


int main() {

    std::random_device rand_seed;//非決定論的乱数生成器
    std::mt19937 mt_obj(rand_seed());//メルセンヌ・ツイスタで乱数を生成する
    std::uniform_int_distribution<int>::param_type uniform_parameter(-100, 100);//整形する幅を決める
    std::uniform_int_distribution<int> uniform(uniform_parameter);//生成された乱数を指定された幅の一様分布に整形する

    static int calc_num = 0;
    unsigned int result = uniform(mt_obj);


    for (int i = 0; i < 100; i++) {

        if (i != 0) {//初回は飛ばす
            result = uniform(mt_obj);
        }

        calc_num++;

        //表示の幅やレイアウトについて指定して出力している
        std::cout
            << std::setw(5) << std::left << calc_num
            << ":result = "
            << std::setw(5) << std::right << result
            << std::endl;
    }
    return 0;
}



<実行結果>

1    :result =   100
2    :result =    72
3    :result =    13
4    :result =    12
5    :result = 4294967282
6    :result =     9
7    :result =    67
8    :result =    91
9    :result =    25
10   :result =    39
11   :result = 4294967232
12   :result =     5
13   :result =    67
14   :result =    45
15   :result = 4294967221
16   :result = 4294967211
17   :result = 4294967266
18   :result =    35
19   :result = 4294967293
20   :result = 4294967256
21   :result = 4294967258
22   :result = 4294967197
23   :result =    68
24   :result =    46
25   :result =    83
26   :result =    71
27   :result = 4294967256
28   :result =     1
29   :result =    63
30   :result = 4294967235
31   :result =    97
32   :result = 4294967291
33   :result = 4294967202
34   :result =    45
35   :result = 4294967272
36   :result =    17
37   :result = 4294967287
38   :result =    33
39   :result = 4294967288
40   :result =     3
41   :result = 4294967271
42   :result =    52
43   :result =    31
44   :result =    16
45   :result = 4294967278
46   :result = 4294967295
47   :result = 4294967204
48   :result =     9
49   :result = 4294967270
50   :result =    44
51   :result = 4294967273
52   :result =    88
53   :result = 4294967221
54   :result =    36
55   :result = 4294967250
56   :result =    29
57   :result =    98
58   :result =    58
59   :result = 4294967277
60   :result = 4294967246
61   :result =    86
62   :result =    66
63   :result =    82
64   :result = 4294967212
65   :result =    60
66   :result = 4294967268
67   :result =     3
68   :result =    83
69   :result =     5
70   :result = 4294967291
71   :result = 4294967263
72   :result = 4294967288
73   :result =    73
74   :result =    88
75   :result = 4294967236
76   :result =     6
77   :result =    39
78   :result =     3
79   :result =    62
80   :result = 4294967212
81   :result = 4294967200
82   :result =    17
83   :result = 4294967224
84   :result =    75
85   :result =     5
86   :result = 4294967289
87   :result =    29
88   :result = 4294967213
89   :result =    59
90   :result = 4294967253
91   :result =     0
92   :result =    10
93   :result = 4294967289
94   :result =    49
95   :result =    37
96   :result =    94
97   :result =    45
98   :result = 4294967293
99   :result = 4294967259
100  :result = 4294967221

と、実行結果がdistributionで定めた範囲の一様分布から外れている。これは負の値はdistributionの範囲に指定できないということなのだろうか?

ただ、std::uniform_int_distribution<int>::param_typeのコンストラクタでは幅の最小最大についてunsigned intではなくintを引数に取っているし、param_typeを使わずにstd::uniform_int_distribution<int>のコンストラクタもunsigned intではなくintを引数に取っている。

仮にこのdistributionが負の値を範囲指定出来ないなら、この辺がunsigned intで引数に渡せと言われそうなものだけど、これは本当に負の値を範囲指定できないという解釈で良いのだろうか。

もちろん、欲しい乱数の幅をズラすのは、得られた範囲にズラしたい分の幅を足したり引いたりすればいい(例えばdistributionの幅を0~200にしておいて、result = uniform(mt_obj);result = uniform(mt_obj) - 100;というようにして、得られた値を-100すれば、-100~100の範囲で乱数は得られる)

でもこんなの標準で出来るようにしておいて欲しい、という願望もあるので、何かできる方法は無いのかなあと思うものの、やはりよくわからない。 `

誰か知っていたら教えて下さい。