LoginSignup
1
1

Unity.Mathematics.Random の特性と CreateFromIndex()

Posted at

インターネットをご覧の皆さん こんばんは。
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 の値が近いと上位バイトが同じになるため、初回のランダム値が偏ってしまうようです。

Random.state
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() で生成したものは、初回の結果からランダムになりました。

テストコードの生成個所を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 として使用してしまったため遭遇した現象でした。見知った処理でも知らないことはあるものだと気を付けようと思いました。

以上 おつきあいいただきありがとうございました。

1
1
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
1
1