線形合同法であるseedが0からいくつ進めたものかを得る その3 (法が2のべきとは限らない一般の場合)

法が2のべきとは限らない一般の場合で求める方法ができた。

法が素数pのべきの場合、{x_n}が法p^eで最大周期のLCG数列なら{x_{pn} / p}が法p^{e-1}で最大周期のLCGの数列になることを使う。
一般の法mに対しては、mを素因数分解したのち、各素数について計算し、中国剰余定理で結果を得る。

require "prime"

# プログラムの結果が正しいかテストする
def test()
  m = 2 * (3 ** 4) # テストする法mの値

  factors = Prime.prime_division(m)
  product = factors.map{|p,e| p }.inject(1, :*)
  if m % 4 == 0 then product *= 2 end

  # 最大周期になる全てのaとbを試す
  (0..m/product-1).each do |k|
    a = product * k + 1
    m.times do |b|
      next if gcd(b, m) != 1
      list = gen_list(a, b, m)
      list.each_with_index do |s, i|
        index = calc_index(s, a, b, m)
        if i != index
          raise "wrong result (#{s},#{a},#{b},#{m}) expected: #{i}, got: #{index}"
        end
      end
      puts "ok a=#{a}, b=#{b}"
    end
  end
end

# x_0 = 0, x_{n+1} = (ax_n + b) mod m で定義される数列の最初のm項を得る
def gen_list(a, b, m)
  list = []
  s = 0
  m.times do
    list << s
    s = (a * s + b) % m
  end
  list
end

# x_0 = 0, x_{n+1} = (ax_n + b) mod m で定義される数列でx_n = sとなるnを求める
# ただし数列の周期はmでなければならない
def calc_index(s, a, b, m)
  factors = Prime.prime_division(m)
  res = 0
  product = 1
  factors.each do |p, e|
    i = calc_index_pe(s, a, b, p, e)
    res = chinese(res, product, i, p**e)
    product *= p**e
  end
  res
end

# calc_indexでm = p^e (pは素数)の場合
def calc_index_pe(s, a, b, p, e)
  if e == 0 then
    0
  else
    l = (inverse(b, p) * s) % p
    pe = p ** e
    ss = step(a, b, s, p - l, pe)
    (aa, bb) = step_param(a, b, p, pe)
    (calc_index_pe(ss / p, aa, bb / p, p, e - 1) * p - p + l) % pe
  end
end

# f(x) = (ax + b) mod mに対してf^n(s)を求める
def step(a, b, s, n, m)
  if n == 0 then
    s
  elsif n % 2 == 0 then
    step((a * a) % m, ((a + 1) * b) % m, s, n / 2, m)
  else
    (step((a * a) % m, ((a + 1) * b) % m, s, n / 2, m) * a + b) % m
  end
end

# 関数 f(x) = (ax + b) mod m のn個の合成f^nは
# f^n(x) = (a'x + b') mod mと書ける。そのa', b'を得る
def step_param(a, b, n, m)
  if n == 0
    [1, 0]
  elsif n % 2 == 0
    step_param((a * a) % m, (a + 1) * b % m, n / 2, m)
  else
    (aa, bb) = step_param((a * a) % m, (a + 1) * b % m, n / 2, m)
    [(aa * a) % m, (aa * b + bb) % m]
  end
end

# 中国剰余定理
# x ≡ a (mod p), x ≡ b (mod q) となるxを求める (p, qは互いに素)
def chinese(a, p, b, q)
  (a * q * inverse(q, p) + b * p * inverse(p, q)) % (p*q)
end

# ax + by = k の解を得る (ただしkはaとbの最大公約数)
def extgcd(a, b)
  if b == 0 then
    return [1, 0]
  end
  (q, r) = a.divmod(b)
  (x, y) = extgcd(b, r)

  # 今 bx + ry = k が成り立っている。r = a - bqを使えば
  # ay + b(x - qy) = kを得る
  [y, x - q * y]
end

def gcd(a, b)
  if b == 0 then
    a
  else
    gcd(b, a % b)
  end
end

# a x ≡ 1 (mod m) となるxを得る (aとmは互いに素)
def inverse(a, m)
  (x, y) = extgcd(a, m)
  x
end
筆者: oupo (連絡先: oupo.nejiki@gmail.com)