LoginSignup
3
2

More than 3 years have passed since last update.

System.Random を継承して、自作の乱数生成器を実装する際の注意点

Last updated at Posted at 2019-10-02

まえがき(飛ばしてOK)

筆者が昔、軽い気持ちで自作乱数生成クラスを継承で作ったら意外に奥が深いと気付かされた。
扱うのが乱数となると、間違った値がいつも出力されるわけではないので簡単には気づけない。。。

オーバーライドする必要のあるメソッドは?

1つ目の注意点
Random.Sample() メソッドは protected virtual なので、これ一つをオーバーライドすれば
すべてのメソッドの動作を、独自の乱数に置き換えれる雰囲気を出しているが、実はSample()が使用されるのは次のメソッドだけ

  • Next(int minValue, int maxValue)
  • Next(int maxValue)
  • NextDouble()

実は以下のメソッドでは使用されていない、なので以下のメソッドもオーバーライドする必要がある。

  • Next()
  • NextBytes(byte[] buffer)

double型に変換するときの注意点

2つ目の注意点
Sample() 内などで、自作乱数生成器で一旦整数を生成し double型に変換してから出力する場合、
Sample() や NextDouble() の出力の範囲の決まりは 0.0 以上 1.0 未満 となっているので、1.0 以上を出力しないように注意する必要がある

以下手段を検証(結論はこちら→ long型から変換する場合2

int型から変換する場合

  • int型の範囲は、0~2147483647(負数除く)
  • 割られる数を2147483646以下にすれば、一応1.0未満で出力される。
  • しかし、出力の値が飛び飛びになる。(0.999999999534339~0.999999999068677間の値が出力されない)

以下、参考に式と出力結果

出力結果
2147483646 / 2147483647 0.999999999534339
2147483645 / 2147483647 0.999999999068677
2147483644 / 2147483647 0.999999998603016
2147483643 / 2147483647 0.999999998137355

long型から変換する場合1

単純に最大値で割ってしまうと・・・

  • long型の範囲は、0~9223372036854775807(負数除く)
  • double型が正確に表現できる最大値を超えるため、割られる数が9223372036854775296以上のときに 1.0 になってしまう。
出力結果 double値(16進数)
9223372036854775807 / 9223372036854775807 1.0 0x3FF0000000000000
9223372036854775806 / 9223372036854775807 1.0 0x3FF0000000000000
9223372036854775805 / 9223372036854775807 1.0 0x3FF0000000000000
9223372036854775297 / 9223372036854775807 1.0 0x3FF0000000000000
9223372036854775296 / 9223372036854775807 1.0 0x3FF0000000000000
9223372036854775295 / 9223372036854775807 1.0 0x3FEFFFFFFFFFFFFF
9223372036854775294 / 9223372036854775807 1.0 0x3FEFFFFFFFFFFFFF
9223372036854771201 / 9223372036854775807 1.0 0x3FEFFFFFFFFFFFFC
9223372036854771200 / 9223372036854775807 1.0 0x3FEFFFFFFFFFFFFC
9223372036854771199 / 9223372036854775807 0.999999999999999 0x3FEFFFFFFFFFFFFB
9223372036854771198 / 9223372036854775807 0.999999999999999 0x3FEFFFFFFFFFFFFB

long型から変換する場合2

上記の方法を対策した場合

  • double型が正確に表現できる整数の最大値(9007199254740992)を割る数、生成した整数(0~9007199254740991)を割られる数にして計算する。
  • 割られる数の最大値は 9007199254740992 にすると、結果が 1.0 になるので 一つ小さくする。
    • 生成値を 0x1F_FFFF_FFFF_FFFF でビットマスクするか、11 ビット右シフトしてから、割り算すればよい。
  • 下の表の2段目、出力結果では1.0になっているが、0x3FEFFFFFFFFFFFFF なので 値としては 0.999... なのでOK
  • ulong型の場合も同様にすれば可能
出力結果 double値(16進数)
9007199254740992 / 9007199254740992 1.0 0x3FF0000000000000
9007199254740991 / 9007199254740992 1.0 0x3FEFFFFFFFFFFFFF
9007199254740990 / 9007199254740992 1.0 0x3FEFFFFFFFFFFFFE
9007199254740989 / 9007199254740992 1.0 0x3FEFFFFFFFFFFFFD
9007199254740988 / 9007199254740992 1.0 0x3FEFFFFFFFFFFFFC
9007199254740987 / 9007199254740992 0.999999999999999 0x3FEFFFFFFFFFFFFB
9007199254740986 / 9007199254740992 0.999999999999999 0x3FEFFFFFFFFFFFFA
3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2