乱数について

c#の乱数の作成について気をつけておくことがあります。

一般のプログラムで使われる乱数

0から99までの乱数を50個作成するプログラム

次のプログラムは、高速で、新しい乱数を作成するプログラムになります。

for (int i = 0; i < 50; i++)
{
    var rnd = new Random();
    Console.Write($"{rnd.Next(100)} ");
    Console.WriteLine(Environment.TickCount);
}
Environment.TickCountについて
// 概要:
// システム起動後のミリ秒単位の経過時間を取得します
// 戻り値:
// コンピューターが最後に起動してからの経過時間(ミリ秒(int型))

実行結果

TickCountが同じ場合、同じ乱数が生成されています

37 18134312
37 18134312
37 18134312
・
・ 同じ結果が続きます
・
37 18134312
37 18134312
72 18134328
72 18134328
・
・ 同じ結果が続きます
・
72 18134328
72 18134328
72 18134328
・
・
・

同じ乱数が生成される理由

1ミリ秒の間に作成されたインスタンスは、同じ乱数が生成されます。
これは、乱数が種(インスタンス化されるときの引数)をベースに作成されるので、同じ種でインスタンスが作成された場合、乱数も同じものが生成されるためです。
new Random();のように引数のないコンストラクタで作成すると、種は次のようにセットされます。

  • .Net Framework (.Net 4.7.2) の場合
    Environment.TickCount
  • .Net Core (.Net Core 2.2) の場合
    内部で乱数を生成して種としている
    .Net Frameworkのように1ミリ秒以内にインスタンスが作られても同じ乱数とならない

対応策

インスタンスを都度作成しないようにすればいいです。
rnd.Next(100)は乱数の種が変わらなければ、次に新しい乱数を取得できますので、インスタンスの作成を1度きりにすることで対応します。

var rnd = new Random();

for (int i = 0; i < 50; i++)
{
    Console.Write($"{rnd.Next(100)} ");
    Console.WriteLine(Environment.TickCount);
}

実行結果

TickCountが同じ場合でも、乱数がきちんと生成されています

55 24564531
54 24564531
92 24564531
4 24564531
65 24564531
32 24564546
54 24564546
50 24564546
70 24564546
・
・ 省略(違う結果)
・
80 24564546
71 24564546
8 24564546
98 24564546
43 24564546
87 24564562
42 24564562
80 24564562
85 24564562
81 24564562
29 24564578
83 24564578

精度の高い乱数を取得する方法

暗号化に使用する場合など精度を要求される場合、RNGCryptoServiceProviderを使い乱数を生成することができます。

参考

0から99までの乱数を50個作成するプログラム

次のusingが必要

using System.Security.Cryptography;

RNGCryptoServiceProvider()は利用後にクローズする必要があるため、usingブロックを使います。

using (var rng = new RNGCryptoServiceProvider())
{
    // int型は4byteのため、すべてのデータが格納できるように確保します。(仕様)
    var randomByte = new byte[4];

    for (int i = 0; i < 50; i++)
    {
        int rand;

        while (true)
        {
            // byte[]型で乱数を取得
            rng.GetBytes(randomByte);
            // byte[]型をint型に変換
            rand = BitConverter.ToInt32(randomByte, 0);

            if (rand > 0) break;
        }

        Console.Write($"{rand % 100} ");
        Console.WriteLine(Environment.TickCount);
    }
}

実行結果

TickCountが同じ場合でも、乱数がきちんと生成されています

27 30304781
40 30304781
96 30304781
・
・ 省略(違う結果)
・
23 30304781
50 30304781
86 30304781
41 30304781
40 30304796
41 30304796
40 30304796
・
・ 省略(違う結果)
・
46 30304796
6 30304796
48 30304796
9 30304796
34 30304812
45 30304812
83 30304812
89 30304812
96 30304812
65 30304812
82 30304812
44 30304812
70 30304812

同じ乱数を発生させないサンプル(乱数の種をランダムにセットする方法)

sizeof(int)はint型の大きさ(サイズ)を取得します。
=4byteです。
次のコードは0から2までの精密な乱数を作成します。
Randomクラスのインスタンス生成時の乱数の種として、時計を使わずに精密に生成された乱数を使っています。

var randomByte = new byte[sizeof(int)];

using (var rng = new RNGCryptoServiceProvider())
{
    rng.GetBytes(randomByte);
}

var rand = BitConverter.ToInt32(randomByte, 0);
var random = new Random(rand);
Console.Write($"{random.Next(3)} ");

C#

Posted by hidepon