インターネットをご覧の皆さん こんばんは。
Unity.Mathematics.Random を使用した際に学んだ特性をメモしておこうかと思います。
試した時点の環境は
- com.unity.mathematics 1.3.1
です。
seed を変えてもランダムにならない?
Unity.Mathematics.Random を下記のように使用していました。
var seed = 123u;
var rand = new Unity.Mathematics.Random(seed); // seed で初期化
var max = 10;
var result = rand.NextInt(max); // [0, max) の範囲で結果を取得
この時、seed の値を変更しても結果がランダムになりませんでした。
下記は seed の値を変えてみた結果です。
void Rand(uint seed_from, uint seed_to, int max)
{
var first_results = new Dictionary<int, int>();
var second_results = new Dictionary<int, int>();
for (var i = 0; i < max; i++) {
first_results[i] = 0;
second_results[i] = 0;
}
for (uint seed = seed_from; seed <= seed_to; seed++) {
var rand = new Unity.Mathematics.Random(seed);
first_results[rand.NextInt(max)]++;
second_results[rand.NextInt(max)]++;
}
Debug.Log($"[seed={seed_from}-{seed_to}] NextInt({max})");
Debug.Log("1st: " + string.Join(", ", first_results.OrderBy(x => x.Key).Select(x => $"{x.Key}={x.Value}")));
Debug.Log("2nd: " + string.Join(", ", second_results.OrderBy(x => x.Key).Select(x => $"{x.Key}={x.Value}")));
}
Rand( 1, 1000, 10);
Rand( 5001, 6000, 10);
Rand(10001, 11000, 10);
> [seed=1-1000] NextInt(10)
> 1st: 0=1000, 1=0, 2=0, 3=0, 4=0, 5=0, 6=0, 7=0, 8=0, 9=0
> 2nd: 0=103, 1=104, 2=104, 3=96, 4=104, 5=104, 6=99, 7=91, 8=98, 9=97
> [seed=5001-6000] NextInt(10)
> 1st: 0=0, 1=0, 2=42, 3=958, 4=0, 5=0, 6=0, 7=0, 8=0, 9=0
> 2nd: 0=104, 1=96, 2=101, 3=98, 4=98, 5=97, 6=103, 7=95, 8=104, 9=104
> [seed=10001-11000] NextInt(10)
> 1st: 0=0, 1=0, 2=0, 3=0, 4=0, 5=85, 6=915, 7=0, 8=0, 9=0
> 2nd: 0=97, 1=98, 2=95, 3=104, 4=101, 5=102, 6=94, 7=101, 8=104, 9=104
初回の結果に偏りがあることがわかりました。
Unity.Mathematics.Random の特性
Unity.Mathematics.Random は xorshift をベースに実装されているようです。
public Random(uint seed)
{
state = seed;
CheckInitState();
NextState();
}
private uint NextState()
{
CheckState();
uint t = state;
state ^= state << 13;
state ^= state >> 17;
state ^= state << 5;
return t;
}
public int NextInt(int max)
{
CheckNextIntMax(max);
return (int)((NextState() * (ulong)max) >> 32);
}
seed の値が近いと上位バイトが同じになるため、初回のランダム値が偏ってしまうようです。
var first_results = new List<uint>();
var second_results = new List<uint>();
for (uint seed = 1; seed <= 10; seed++)
{
var rand = new Unity.Mathematics.Random(seed);
first_results.Add(rand.state);
rand.NextInt();
second_results.Add(rand.state);
}
Debug.Log($"1st: {string.Join(", ", first_results.Select(x => $"{x:X8}"))}");
Debug.Log($"2nd: {string.Join(", ", second_results.Select(x => $"{x:X8}"))}");
1st: 00042021, 00084042, 000C6063, 00108084, 0014A0A5, 0018C0C6, 001CE0E7, 00210108, 00252129, 0029414A
2nd: 04080601, 08008C02, 0C088A03, 10011804, 14091E05, 18019406, 1C099207, 20023008, 240A3609, 2802BC0A
Unity.Mathematics.Random.CreateFromIndex()
調べてみると、Unity.Mathematics.Random には CreateFromIndex(uint index) というメソッドが存在することがわかりました。
CreateFromIndex(uint index) は、渡された index をもとに Hash を計算して seed としてくれるようです。
Random.CreateFromIndex() で生成したものは、初回の結果からランダムになりました。
[seed=1-1000] NextInt(10)
1st: 0=97, 1=112, 2=99, 3=100, 4=115, 5=112, 6=89, 7=104, 8=90, 9=82
2nd: 0=80, 1=100, 2=95, 3=101, 4=103, 5=117, 6=116, 7=92, 8=100, 9=96
[seed=5001-6000] NextInt(10)
1st: 0=88, 1=105, 2=100, 3=124, 4=85, 5=96, 6=103, 7=100, 8=87, 9=112
2nd: 0=108, 1=87, 2=93, 3=102, 4=98, 5=118, 6=100, 7=102, 8=92, 9=100
[seed=10001-11000] NextInt(10)
1st: 0=91, 1=88, 2=95, 3=104, 4=129, 5=108, 6=108, 7=107, 8=90, 9=80
2nd: 0=91, 1=107, 2=96, 3=84, 4=98, 5=101, 6=109, 7=106, 8=97, 9=111
おわりに
今回は DateTime.Now.Millisecond を seed として使用してしまったため遭遇した現象でした。見知った処理でも知らないことはあるものだと気を付けようと思いました。
以上 おつきあいいただきありがとうございました。