まえがき(飛ばして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 |