XorShift256に使えるパラメタを列挙してみた

# やったこと

- XorShift法で64ビット変数を4本使う場合のパラメタの列挙をするプログラム、作って動かした。
- 1ヶ月位かけて今朝未明にようやく終わったので記事化

# ターゲット

- とりあえず高速で長い周期を持つ擬似乱数生成器が欲しい人
- XorShift法に関する研究がしたいが、パラメタの列挙から始めるのが面倒な人

# テンプレート
```c++
std::uint64_t xorshift256(void) {
  static std::uint64_t x = 1, y = 0, z = 0, w = 0;  // 全ゼロ以外の値。種。
  std::uint64_t t = x ^ (x << A);
  x = y; y = z; z = w;
  return w = (w ^ (w >> C)) ^ (t ^ (t >> B));
}
```

# パラメタ一覧

| A | B | C |
|:-:|:-:|:-:|
|  1|  2| 27|
|  1|  3| 10|
|  1|  5| 33|
|  1| 21| 25|
|  1| 63|  7|
|  2| 19| 14|
|  2| 39|  3|
|  3|  7| 42|
|  3| 41| 29|
|  4|  5| 56|
|  4|  5| 61|
|  4|  9| 27|
|  4| 23|  9|
|  5|  1| 21|
|  5|  6| 53|
|  5| 15| 16|
|  5| 20|  9|
|  5| 21|  9|
|  6| 31| 25|
|  7|  3| 13|
|  7|  3| 32|
|  7| 13| 60|
|  7| 15|  1|
|  7| 27| 24|
|  7| 27| 44|
|  8| 13| 28|
|  8| 27| 24|
|  9|  1| 33|
| 10|  1| 32|
| 10|  7|  9|
| 11|  1| 59|
| 11|  3|  4|
| 11|  9| 40|
| 11| 10| 19|
| 11| 18| 59|
| 11| 25|  7|
| 11| 26| 41|
| 11| 30|  9|
| 11| 63| 21|
| 13|  5| 36|
| 13|  6| 19|
| 13|  8| 55|
| 13| 14| 11|
| 13| 18| 41|
| 13| 20|  1|
| 13| 21| 44|
| 14|  9| 52|
| 14| 17| 26|
| 14| 19| 53|
| 15|  1| 61|
| 15| 16| 25|
| 15| 17|  5|
| 15| 17| 59|
| 15| 26| 19|
| 15| 37| 10|
| 15| 41| 17|
| 17|  6| 29|
| 17|  6| 49|
| 17| 11| 18|
| 17| 18|  9|
| 17| 22| 33|
| 17| 23|  1|
| 18| 17| 63|
| 19| 14| 23|
| 19| 16| 53|
| 19| 16| 61|
| 19| 21| 13|
| 20|  9| 15|
| 20| 29| 43|
| 21| 13|  1|
| 21| 39|  2|
| 21| 42|  5|
| 22| 15|  7|
| 22| 19| 28|
| 22| 19| 47|
| 23|  1| 41|
| 23|  7| 55|
| 23| 13| 36|
| 23| 17| 58|
| 23| 41| 31|
| 24| 13| 50|
| 24| 29| 41|
| 25|  1| 36|
| 25|  7| 52|
| 25| 11| 33|
| 25| 12| 41|
| 25| 16|  1|
| 25| 17| 45|
| 25| 19| 42|
| 25| 22| 11|
| 25| 27|  6|
| 25| 42|  7|
| 25| 43| 18|
| 26|  9| 35|
| 26|  9| 48|
| 26| 13| 36|
| 26| 37|  6|
| 27|  1| 32|
| 27|  5| 62|
| 27|  6| 13|
| 27|  8| 59|
| 28| 31| 59|
| 28| 47|  4|
| 29|  3| 59|
| 30|  1|  2|
| 30| 29| 14|
| 31|  1| 55|
| 31| 11|  5|
| 31| 12| 45|
| 31| 33| 11|
| 31| 33| 19|
| 33| 32| 23|
| 34|  9|  4|
| 35| 38| 21|
| 36|  1|  3|
| 36| 11| 39|
| 36| 25|  8|
| 36| 41| 11|
| 37|  5| 10|
| 37| 27|  4|
| 37| 49| 13|
| 39| 28|  1|
| 40| 15| 39|
| 41| 26|  3|
| 41| 45| 11|
| 42| 27| 22|
| 43| 10| 47|
| 43| 21| 50|
| 43| 21| 51|
| 44|  9| 29|
| 44| 29|  2|
| 45|  7|  1|
| 45| 15|  1|
| 45| 19| 59|
| 47|  5|  2|
| 47|  6|  3|
| 50|  3|  1|
| 50|  3| 18|
| 50|  7| 19|
| 50| 15|  1|
| 51|  5| 49|
| 51| 13| 19|
| 51| 13| 21|
| 51| 46|  1|
| 51| 46| 13|
| 52|  7|  1|
| 55| 17|  9|
| 55| 23|  7|
| 59|  3|  8|
| 59| 60|  1|
| 61| 42|  3|
| 63| 31|  1|

以上、152通り。

# 計算プログラム

```ruby
#!/usr/bin/env ruby

require 'benchmark'
require 'matrix'
require 'parallel'

def unit_length
  64
end

def create_t(a, b, c)
  i = Matrix.identity(unit_length)
  o = Matrix.zero(unit_length)
  l = Matrix.build(unit_length) do |row, col|
    if row + 1 == col
      1
    else
      0
    end
  end
  r = Matrix.build(unit_length) do |row, col|
    if row == col + 1
      1
    else
      0
    end
  end
  g = Matrix[
    [o, o, o, (i + l**a) * (i + r**b)],
    [i, o, o, o],
    [o, i, o, o],
    [o, o, i, (i + r**c)]
  ]
  Matrix.build(unit_length * 4) do |row, col|
    g[row / unit_length, col / unit_length][row % unit_length, col % unit_length]
  end
end

@ds = [
  0x000000000000000000d3eafc3af14600ffffffffffffffffff2c1503c50eb9ff,
  0x000000000000013540775b48cc32ba00fffffffffffffecabf88a4b733cd45ff,
  0x0000000000042f00fffffffffffbd0ff0000000000042f00fffffffffffbd0ff,
  0x00000280fffffd7f00000280fffffd7f00000280fffffd7f00000280fffffd7f,
  0x00003d30f19cd100ffffc2cf0e632eff00003d30f19cd100ffffc2cf0e632eff,
  0x0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff,
  0x00663d80ff99c27f00663d80ff99c27f00663d80ff99c27f00663d80ff99c27f,
  0x00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff,
  0x0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f,
  0x3333333333333333333333333333333333333333333333333333333333333333,
  0x5555555555555555555555555555555555555555555555555555555555555555,
]

def pop32(n)
  n = (n & 0x55555555) + ((n >> 1) & 0x55555555)
  n = (n & 0x33333333) + ((n >> 2) & 0x33333333)
  n = (n & 0x0f0f0f0f) + ((n >> 4) & 0x0f0f0f0f)
  n = (n & 0x00ff00ff) + ((n >> 8) & 0x00ff00ff)
  (n & 0x0000ffff) + (n >> 16)
end

def pop(n)
  s = 0
  until n == 0
    s += pop32(n & 0xffffffff)
    n >>= 32
  end
  s
end

def multiply_bin(a,b)
  as = a.to_a.map do |item|
    item.join.to_i 2
  end
  bs = b.t.to_a.map do |item|
    item.join.to_i 2
  end
  Matrix.build(a.row_size, b.column_size) do |l, n|
    pop(as[l] & bs[n]) & 1
  end
end

def regular_bin_p(a)
  t = a.to_a
  len = a.row_size
  for y in 0...len
    if t[y][y] == 0
      processed = false
      for z in y+1...len
        unless t[z][y] == 0
          s = t[z]
          t[z] = t[y]
          t[y] = s
          processed = true
          break
        end
      end
      return false unless processed
    end

    for z in 0...len
      next if z == y
      unless t[z][y] == 0
        for x in 0...len
          t[z][x] ^= t[y][x]
        end
      end
    end
  end
  return true
end

def clear_cache
  @power_cache = {}
end

def highpower_bin(a,n)
  t = a
  until n == 0
    if n == 1
      return t
    else
      t = multiply_bin(t,t)
      n >>= 1
    end
  end
end

def power_bin(a,n)
  return highpower_bin(a,n) if (n & (n - 1)) == 0

  s = Matrix.identity(a.row_size)
  i = 1
  j = 0
  @power_cache[i] = a unless @power_cache[i]
  t = @power_cache[i]
  until n == 0
    if n & 1 == 1
      j += i
      @power_cache[j] = multiply_bin(s,t) unless @power_cache[j]
      s = @power_cache[j]
    end
    i *= 2
    @power_cache[i] = multiply_bin(t,t) unless @power_cache[i]
    t = @power_cache[i]
    n >>= 1
  end
  return s
end

def precheck(a,b,c)
  t = create_t(a,b,c)

  return nil unless regular_bin_p t
  return nil unless power_bin(t, (2**(unit_length * 4))) == t

  return [a,b,c]
end

def postcheck(a,b,c)
  t = create_t(a,b,c)
  clear_cache

  for d in @ds
    return nil if power_bin(t, d + 1) == t
  end

  return [a,b,c]
end

works = []
for a in 1...unit_length
  for b in 1...unit_length
    for c in 1...unit_length
      works << [a,b,c]
    end
  end
end

temp_results = []
results = []
temp_results = Parallel.map(works, progress:   ' Pre-check  ...') { |work|
  precheck work[0], work[1], work[2]
}.compact
results = Parallel.map(temp_results, progress: ' Post-check ...') { |work|
  postcheck work[0], work[1], work[2]
}.compact

puts "#{works.size} -> #{temp_results.size} -> #{results.size}"

s = ''
for result in results
  s << "#{result[0]}/#{result[1]}/#{result[2]}\n"
end

IO.write("xorshift-#{unit_length * 4}.log", s)
```

多倍長整数をネイティブに書きたかったのでrubyで組んでみたり、
そのくせ普通に書いたら遅すぎて使い物にならず高速化のためにぐちゃぐちゃ弄っていたり、
そもそもコメントがなかったりしますがご愛嬌。

# 計算時間とか
## マシンスペック
CPU: i7-5820K (3.3 GHz, 12 thread)

## `ruby`の自己診断
前段: 600時間23分2秒使い、250047パターンから有効な432パターンを抽出。
後段: 7時間25分29秒使い、432パターンから有効な152パターンを抽出。

## `time`による診断
real: 36468分32.311秒
user: 435482分24.948秒
sys:  14分35.224秒

シングルスレッドで実行してたら10ヶ月掛かってたな・・・

# まとめ

以下のことがわかった。

- A, B, C の最大値が最も小さいものは(1, 3, 10)の組み合わせ。
- A+B+C が最も小さくなるのも(1, 3, 10)の組み合わせ。