以前書いた疑似乱数生成コードの修正作業をしたので、その際に調べたことや考えたことのメモ。
基本的に自分用なので雑。
動機と方針
- System.Randomのアルゴリズムは古めで偏りがある。
- Knuthの減算法。
- 再現性の問題があるので今後も変わることはなさそう。
- UnityEngine.Randomなどゲームエンジンや外部ライブラリに依存したくない。
- System.Randomは継承しない。
- .NET的には推奨らしいけど無駄なフィールドのメモリが必要なため。
0~1の範囲の乱数
1を含めるか否か
各APIの仕様は、
- System.Randomは1を含めない。
- JavaScriptのMath.randomも1を含めない。
- UnityEngine.Randomは1を含める。
利用側として、
- 1を含めて困るケースは考えられる。
- 適当な整数を掛けてから整数にキャストした場合に滅多に現れないが現れうる数が発生。
- 整数の乱数は別にとれるのでキャストの必要があるかどうかは微妙。
- 適当な整数を掛けてから整数にキャストした場合に滅多に現れないが現れうる数が発生。
- 1を含めないと困るケースは思いつかない。
値域は[0,1)の方が無難に思える。
最大値は何か
1を含めないとなると最大値として何をとるべきか。
リファレンスによるとSystem.RandomのNextDoubleでは最大値として0.99999999999999978を返すらしい。 いろいろと試した結果、どうやら計算機イプシロンをeとして(1-e)の値っぽい。
Console.WriteLine("{0:E16}", 1.0 - Math.Pow(2, -52)); // 9.9999999999999978E-001
乱数は次のメソッドでUInt32の範囲の値を返すようにしたので、
public abstract UInt32 Sample();
SingleとDoubleの乱数を返すメソッドはそれぞれ次のように実装。
public Single NextSingle() { return (Sample() * ((1.0f - 1.0f / (1u << 23)) / UInt32.MaxValue)); } public Double NextDouble() { return (Sample() * ((1.0 - 1.0 / (1ul << 52)) / UInt32.MaxValue)); }
誤差やら精度やらの問題があるのでいまいち自信ないけどとりあえずはこれで。
最大値は妥当か
MDNのrandomの説明を読むと気になることが書いてある。
JavaScript における数値は、IEEE 754 浮動小数点での最近接の偶数への丸め (round-to-nearest-even) の振る舞いをするので、 以下の関数についての値域 (ただし Math.random() それ自身についての値域は除く) が厳密ではない、ということに注意してください。 非常に大きい境界値(2^53 以上)を選ぶと、極めて稀なケースにおいては、通常なら除外される上限値を算出してしまうことがあり得ます。
randomもSystem.RandomのNextDoubleと同じような実装をしていると思われるので、上述の実装でも同じことが起こるのかもしれない。
しかし、非常に大きい数を掛けることはほぼないと思われるのでとりあえず無視しておくことにする。
Int32からUInt64への変換
以前書いたコードを読んでいたところ気になるコードを発見。
UInt64 y = unchecked((UInt32)x); // Int32 x;
UInt64へのキャストの間違いかなと思いつつ、一応テスト。
Console.WriteLine(unchecked((UInt64)(-1))); // 18446744073709551615 Console.WriteLine(unchecked((UInt64)(UInt32)(-1))); // 4294967295
どうもInt32から直接UInt64へとキャストすると、ゼロ拡張ではなく符号拡張になる模様。 UInt32経由ではなくInt64経由でキャストした挙動になるみたい。
ちなみに上のコード、(-1)の括弧を外すとマイナス符号が単項演算子なのか二項演算子なのか判断しかねるらしく、「ulongは型だろ」と怒られる(知ってた)。
さらにはその際の日本語のエラーメッセージは
error CS0119: 'ulong' は 種類 です。これは特定のコンテンツでは無効になります。
原文は
error CS0119: 'ulong' is a type, which is not valid in the given context.
('ulong' は 型 です。これはこの文脈では有効ではありません。)
全然違うじゃん…
おわりに
まともな記事として書けるほど内容もなかったので、とりあえずメモ程度にまとめ。
メモなので、いつものですます調も変かと思って封印してみたけど、使えないとなるとそれもそれで書きづらかったり。 まあ、文章の練習だと思って頑張ろう。
なんか記事によって口調違うと情緒不安定な人みたいだな…