乱数について

2023年8月28日

c#の乱数の作成について気をつけておくことがあります
既定では、乱数(実際は、計算で作成される擬似乱数)の種になる元は、内臓タイマーになります
手でボタンを押す等で乱数を作成するのであればいいのですが、コードで短時間に乱数を発生させると同じ値が作成されることになります

次のサンプルプログラムで確認してみましょう

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

NGサンプル

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

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

System.Randomクラスが使用され、内部で線形合同法というアルゴリズムが実装されています

.NET Core

一方、.NET CoreではSystem.Randomクラスの代わりに、Mersenne Twisterと呼ばれるアルゴリズムが実装されたSystem.Randomの改良版であるSystem.RandomNumberGeneratorが使用されるようになっています。

Mersenne Twisterは線形合同法よりも乱数の品質が高く、周期も長いため、よりランダムな乱数を生成できます。また、System.RandomNumberGeneratorは、暗号学的な乱数生成に使用される暗号論的に安全な乱数ジェネレーターであるSystem.Security.Cryptography.RandomNumberGeneratorというクラスをラップしているため、より高いセキュリティレベルでの乱数生成が可能です。

ただし、一般的なアプリケーションでは、線形合同法による乱数生成でも十分であり、Mersenne Twisterや暗号論的に安全な乱数ジェネレーターを使用する必要はありません。より高度なアプリケーションやセキュリティが必要な場合には、適切な乱数ジェネレーターを選択することが重要です。

.NET

.NET 6でも、乱数生成にはSystem.Randomクラスが使用されます。ただし、.NET 6では、ランタイムの最適化により、より高速な乱数生成が期待できます。

例えば、.NET 6では、ランタイムが提供する新しい機能の一つである「Vector API」を使用することができます。これにより、CPUのSIMD(Single Instruction Multiple Data)命令を使用して、複数の乱数を一度に生成することができます。これにより、より高速で効率的な乱数生成が可能になります。

また、.NET 6では、ランタイムの最適化により、乱数生成がさらに高速化されます。例えば、ランタイムが JIT コンパイル時に、より効率的な乱数生成コードを生成することができます。

しかし、最適な乱数生成方法はアプリケーションの目的によって異なるため、必要に応じて適切な乱数生成方法を選択することが重要です。

対応策

インスタンスを都度作成しないようにすればいいです。
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

C#

Posted by hidepon