乱数を生成する方法として、.NETでは疑似乱数を生成するRandomクラスが用意されています。
このほかに、疑似乱数ではなく、生成される結果が予測不可能な乱数列を必要とする目的には、暗号乱数ジェネレーター(暗号論的擬似乱数生成器)としてRandomNumberGeneratorクラスを使用することもできます。 ここでは、Randomクラス・RandomNumberGeneratorクラスを使って乱数を生成する方法について解説します。
なお、以下の解説およびサンプルコードは、乱数の特徴や分布・予測不可能性などを十分に考慮したものではなく、乱数の生成を主眼においたものとなっています。 そのため、暗号・確率等の分野へ適用するには不適当な可能性があるため、必要に応じて他のドキュメントを参照するなどしてより適切な実装を行うようにしてください。
Randomクラス
乱数を得るには、Randomクラスを使用します。 RandomクラスはMathクラスとは異なり、静的メソッドを呼び出して使用するクラスではないため、インスタンスを作成してから使用する必要があります。
Randomクラスは疑似乱数を生成します。 必要に応じて、コンストラクタで乱数系列のシード値を指定することもできます。
乱数の生成 (Next/NextDouble)
Randomクラスには、乱数を得るためのメソッドがいくつか用意されています。 求める乱数の型・値域によって、次のメソッドを使い分けることができます。 いずれも得られる乱数の値域は半開区間(n以上m未満)となっています。
- NextDoubleメソッド
- 0.0以上1.0未満の乱数を実数(Double)で取得する。 [0.0, 1.0)
- Nextメソッド
- 乱数を整数(Int32)で取得する。 以下のオーバーロードが用意されている。
- Next()
- 0以上の乱数を取得する。 [0, Int32.MaxValue)
- Next(max)
- 0以上max未満の乱数を取得する。 [0, max)
- Next(min, max)
- min以上max未満の乱数を取得する。 [min, max)
なお、NextDoubleメソッドには任意の値域を指定するバージョンは用意されていないため、NextDoubleの戻り値を加減乗除して求める値域に変換する必要があります。
バイト配列を乱数で満たす (NextBytes)
NextBytesメソッドを使うと、バイト配列を0からByte.MaxValueまでのランダムな値で満たすことができます。 nonce値を生成したい場合などに、このメソッドを使うことができます。
実際にnonce値として使用する乱数の場合は、RandomNumberGeneratorクラスを使ったほうが適当な場合が多いと思われます。 RandomNumberGeneratorクラスを使ってnonce値を生成する具体例は§.乱数の生成 (GetBytes)をご覧ください。
.NET Standard 2.1/.NET Core 2.1以降では、バイト配列(byte[])だけでなく、Span<byte>を引数にとるバージョンも追加されています。 これにより、バイト配列の一部分だけを乱数で埋めたり、配列以外のバイト列を乱数で埋めるといったことができます。
乱数のシード
Randomクラスでは、一つのインスタンスが一つの乱数系列として動作します。 またRandomクラスでは、個々のインスタンスに異なるシード値(seed, 乱数の種)を与えることにより、複数の乱数系列を生成することができます。
Randomクラスのコンストラクタでは、乱数系列のシード値を指定することができます。 このとき、同一のシード値を与えた場合は同一の乱数系列となり、同じ乱数を生成します。 これにより、インスタンスを生成したときのシード値を保存しておくことで、そのときと同じ乱数を生成するという使い方もできます。
.NET Frameworkにおいて、Randomクラスのコンストラクタでシード値を指定しなかった場合は、デフォルト値としてシステム時計の値(インスタンスを生成するときのEnvironment.TickCountの値)が使用されます。 このため、実行するタイミングによって毎回異なるシード値のインスタンスが作成されることになります。 ただし、Environment.TickCountの値は短時間に連続して取得すると同じ値が返される場合があります。 これにより、シード値を指定せず、短時間で複数のRandomクラスのインスタンスを連続して生成すると、同一のシード値からなるインスタンス(つまり同一の乱数系列)が生成される可能性がある点に注意する必要があります。
.NET Core/.NET 5以降では、デフォルトのシード値自体も別の乱数系列から取得されるため、インスタンスごとに毎回異なるシード値が与えられます。
乱数アルゴリズムの実装
擬似乱数の生成アルゴリズムにはいくつか種類がありますが、Randomクラスを継承して独自に疑似乱数を実装することもできます。 Randomクラスを継承して疑似乱数生成アルゴリズムを実装する際に最低限必要となるのは、Sampleメソッドをオーバーライドし、0.0以上1.0未満の乱数を返すように実装することです。
以下の例では、線形合同法を実装したLCGRandomクラスを作成し、Randomクラスが生成する乱数との比較を行っています。 なお、この実装は一つの例として挙げたもので、生成される乱数の周期や分散については考慮していません。 実際に使用する場合は注意してください。
以下のコードでは、Sampleメソッドでオーバーフローが発生するため、/removeintchecks+
オプションを有効にしてコンパイルするか、プロジェクトファイルにてRemoveIntegerChecks
をTrue
にしてコンパイルしないと例外OverflowExceptionがスローされます。
この例ではオーバーフロー時に例外のスローを抑止する目的でunchecked
ステートメント、および/removeintchecks+
コンパイルオプションを使用しています。 詳しくは整数型のオーバーフローとチェックを参照してください。
このように、Sampleメソッドをオーバーライドすることで独自の乱数生成を行うことができます。 場合によっては、Sampleメソッドを適切に実装することでメソッドの戻り値の範囲が閉区間となるようにすることもできます。
なお、Randomクラスを継承する場合、引数をとらないNextメソッドなどSample以外のメソッドもオーバーライドしないと動作が変わらないメソッドがあります。 詳しくはSampleメソッドのリファレンスを参照してください。
RandomNumberGeneratorクラス
Randomクラスが生成する乱数は疑似乱数であり、既知の乱数生成アルゴリズムに基づいて生成されます。 そのため、Randomクラスでは次に生成される乱数を予測することは不可能ではありません。 このような特徴は、特に暗号分野などにおいて、乱数源としては不適当な場合があります。
次の乱数を予測しうるRandomクラス・疑似乱数に対し、予測できない/しにくい乱数を生成する乱数源として、RandomNumberGeneratorクラスを用いることができます。
インスタンスの作成と破棄
Randomクラスと同様、RandomNumberGeneratorクラスで乱数を生成するためにはまずインスタンスを作成する必要があります。 RandomNumberGeneratorは抽象クラスであるため、インスタンスはnew
による作成ではなく、Createメソッドを使って作成します。
.NET Framework 4.0以降のRandomNumberGeneratorはIDisposableインターフェイスを実装しています。 そのため、usingステートメントを使って次のようにインスタンスの作成・破棄を行います。
RandomNumberGenerator.Createメソッドに引数(乱数ジェネレータの名前)を指定せずに作成したインスタンスの場合はデフォルトの乱数ジェネレータが生成されます。
デフォルトのジェネレータに対してはDisposeメソッドを呼び出す必要はないようですが、乱数ジェネレータの種類によってはアンマネージリソースの解放が必要な実装となっている可能性があるため、usingステートメントによって必ずDisposeメソッドが呼び出されるようにしておくのが無難です。
usingステートメントとインスタンスの破棄についてはオブジェクトの破棄 §.usingステートメントを参照してください。
乱数の生成 (GetBytes)
RandomNumberGeneratorクラスを使って乱数を生成するには、GetBytesメソッドを使います。 GetBytesメソッドは、Random.NextBytesメソッドと同様のメソッドで、引数として与えられたバイト配列を乱数で満たします。
Randomクラスとは異なり、RandomNumberGeneratorはシード値を必要としません。 RandomNumberGeneratorでは、インスタンスに関わらずメソッド呼び出し毎に異なる乱数を得ることができます。
非ゼロ乱数の生成 (GetNonZeroBytes)
生成される乱数に0が含まれないようにしたい場合は、GetNonZeroBytesメソッドを使うことができます。
整数乱数・値域を指定した乱数の生成 (GetInt32)
RandomNumberGeneratorから整数形式の乱数を生成したい場合は、GetInt32メソッドを使うことができます。 このメソッドは静的メソッドであるため、インスタンスを生成せずに使用することができますが、逆に特定の乱数ジェネレーターインスタンスに対して使用することができません。 GetInt32メソッドは.NET Standard 2.1/.NET Core 3.0以降で使用することができます。
GetInt32メソッドでは、Random.Nextメソッドと同様に、生成したい乱数の値域を指定します。 GetInt32メソッドでは次の2つのオーバーロードが用意されていて、いずれも得られる乱数の範囲は半開区間(n以上m未満)となっています。
- GetInt32(toExclusive)
- 0以上・toExclusive未満の乱数を取得する。 [0, toExclusive)
- GetInt32(fromInclusive, toExclusive)
- fromInclusive以上・toExclusive未満の乱数を取得する。 [fromInclusive, toExclusive)
引数fromInclusive, toExclusiveは、どちらも負数を指定することができ、負の整数乱数を生成することもできます。
Random.NextDoubleに相当する、実数型の乱数を生成するメソッドは用意されていません。 GetInt32メソッドが使用できない場合や、実数型の乱数を生成したい場合は、RandomNumberGeneratorから生成したバイト列を型変換して目的の型・値域の乱数に変換する、などの方法をとる必要があります。
RNGCryptoServiceProviderクラス
RandomNumberGeneratorクラスの具象クラスとして、RNGCryptoServiceProviderクラスが存在します。 RNGCryptoServiceProviderクラスを以下のように直接インスタンス化して暗号乱数ジェネレータとして使用することもできます。
.NET Frameworkでは、RandomNumberGenerator.Createメソッドで作成されるデフォルトの乱数ジェネレータの実装はRNGCryptoServiceProviderクラスとなっています。 RandomNumberGenerator.Createメソッドでは、引数として生成する暗号乱数ジェネレータの名前を指定することもできるようになっていますが、デフォルトの状態ではRNGCryptoServiceProvider以外に生成できるジェネレータは用意されていないようです。
.NET Core/.NET 5では、RNGCryptoServiceProviderは独自の実装を持たず、単にRandomNumberGenerator.Createメソッドがデフォルトで生成する暗号乱数ジェネレータのラッパーとして動作します。
なお、.NET Frameworkで実装されているRNGCryptoServiceProviderクラスは、
暗号化サービス プロバイダー (CSP : Cryptographic Service Provider) によって提供された実装を使用して、暗号乱数ジェネレーター (RNG : Random Number Generator) を実装します。
RNGCryptoServiceProvider クラス
とされているため、乱数の生成にCryptGenRandom関数を使っていると思われます。 Monoの実装では、/dev/random
, /dev/urandom
もしくはWindows上ではCryptGenRandom関数を使用しているようです。
.NET Core/.NET 5での実装(RandomNumberGenerator.Createメソッドが生成するデフォルトの暗号乱数ジェネレーター)では、WindowsではBCryptGenRandom関数、Linux等ではOpenSSLのRAND_bytes関数が使われているようです。
Guid.NewGuidメソッド
Guid構造体自体は乱数ジェネレータではありませんが、NewGuidメソッドは一意なGUIDを返すために乱数を使用しているため、このメソッドの戻り値は一種の乱数とみることもできます。 単にランダムなIDが必要な場合には、Randomクラスを用いて生成せずとも、直接GUIDを使うことができます。
なお、Guid構造体を乱数として用いる場合は、variantフィールドやバージョン番号などの一部のビットは常に同じ値となる点に注意が必要です。
この結果にも現れているとおり、xxxxxxxx-xxxx-4xxx-8xxx-xxxxxxxxxxxx
のビットは常に同じ値となります。