为线性同余发生器挑选A,C和M.

时间:2012-08-23 16:33:40

标签: javascript algorithm math prng number-theory

我希望实现一个具有指定时间段的简单pseudorandom number generator(PRNG),并保证在该时间段内没有冲突。在做了一些研究之后,我遇到了非常着名的LCG,这是完美的。问题是,我无法理解如何正确配置它。这是我目前的实施:

    function LCG (state)
    {
        var a = ?;
        var c = ?;
        var m = ?;

        return (a * state + c) % m;
    }

它表示,为了使所有种子值都有一个完整的时间段,必须满足以下条件:

  1. c m 是相对素数
  2. a-1 可被 m
  3. 的所有素数因子整除
  4. a-1 是4的倍数,如果 m 是4的倍数
  5. 1 3 很容易理解和测试。然而, 2 ,我不太明白这意味着什么或如何检查它。那么C,它可以为零吗?如果它不为零怎么办?

    总的来说,我需要选择A,C和M,使得我的周期 48 ^ 5 - 1 。 M等于句号,我不确定A和C.

2 个答案:

答案 0 :(得分:12)

来自维基百科:

  

如果 c 非零,则LCG将为所有种子值设置一个完整的句点,当且仅当:

     
      
  1. c m 是相对素数,
  2.   
  3. a -1可被 m 的所有素数因子整除,
  4.   
  5. a -1如果 m 是4的倍数,则为4的倍数。
  6.   

你说你想要一个48 5 -1的时期,所以你必须选择 m ≥48 5 -1。让我们尝试选择 m = 48 5 -1并查看我们的位置。维基百科文章中的条件禁止您选择 c = 0,如果您希望句点 m

请注意,11,47,541和911是48 5 -1的主要因子,因为它们都是素数且11 * 47 * 541 * 911 = 48 5功能 -1。

让我们来看看每一个条件:

  1. 要使 c m 成为相对素数, c m 必须没有共同的素因子。因此,选择除 11,47,541和911之外的所有素数,然后将它们相乘以选择 c
  2. 您需要选择 a ,以便 a -1可被 m 的所有素因子整除,即 a = x * 11 * 47 * 541 * 911 + 1适用于您选择的任何 x
  3. 您的 m 不是4的倍数,因此您可以忽略第三个条件。
  4. 总结:

    • m = 48 5 -1,
    • c =除了11,47,541和911以外的任何素数产品(同样, c 必须小于 m ),
    • a = x * 11 * 47 * 541 * 911 + 1,适用于您选择的任何非负 x (同样, a 必须小于 m )。

    这是一个较小的测试用例(在Python中),使用48 2 -1(具有素数因子7和47)的周期:

    def lcg(state):
        x = 1
        a = x*7*47 + 1
        c = 100
        m = 48**2 - 1
        return (a * state + c) % m
    
    expected_period = 48**2 - 1
    seeds = [5]
    for i in range(expected_period):
        seeds.append(lcg(seeds[-1]))
    print(len(set(seeds)) == expected_period)
    

    它应该输出True。 (如果您在阅读Python时遇到任何问题,请告诉我,我可以将其翻译成JavaScript。)

答案 1 :(得分:0)

基于Snowball的答案和评论,我创建了一个完整的示例。您可以将set == list比较用于较小的数字。我无法将48^5-1放入内存。

为了规避a < m问题,我将目标增加了几次,以找到一个数字,其中a可以成为< m(其中m已复制主要原因)。令人惊讶的是,+ 2足以容纳许多数字。稍后在迭代时会跳过一些多余的数字。

import random

def __prime_factors(n):
    """
    https://stackoverflow.com/a/412942/6078370
    Returns all the prime factors of a positive integer
    """
    factors = []
    d = 2
    while n > 1:
        while n % d == 0:
            factors.append(d)
            n //= d
        d += 1
        if d * d > n:
            if n > 1: factors.append(n)
            break
    return factors

def __multiply_numbers(numbers):
    """multiply all numbers in array"""
    result = 1
    for n in numbers:
        result *= n
    return result

def __next_good_number(start):
    """
    https://en.wikipedia.org/wiki/Linear_congruential_generator#c%E2%89%A00
    some conditions apply for good/easy rotation
    """
    number = start
    factors = __prime_factors(number)
    while len(set(factors)) == len(factors) or number % 4 == 0:
        number += 1
        factors = __prime_factors(number)
    return number, set(factors)

# primes < 100 for coprime calculation. add more if your target is large
PRIMES = set([2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41,
        43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97])

def create_new_seed(target):
    """be aware, m might become > target"""
    m, factors = __next_good_number(target)
    a = __multiply_numbers(factors) + 1
    # https://en.wikipedia.org/wiki/Coprime_integers
    otherPrimes = [p for p in PRIMES if p not in factors]
    # the actual random part to get differnt results
    random.shuffle(otherPrimes)
    # I just used arbitary 3 of the other primes
    c = __multiply_numbers(otherPrimes[:3])
    # first number
    state = random.randint(0, target-1)
    return state, m, a, c

def next_number(state, m, a ,c, limit):
    newState = (a * state + c) % m
    # skip out of range (__next_good_number increases original target)
    while newState >= limit:
        newState = (a * newState + c) % m

    return newState

if __name__ == "__main__":
    target = 48**5-1
    state, m, a, c = create_new_seed(target)
    print(state, m, a, c, 'target', target)

    # list and set can't fit into 16GB of memory
    checkSum = sum(range(target))
    randomSum = 0
    for i in range(target):
        state = newState = next_number(state, m, a ,c, target)
        randomSum += newState
    print(checkSum == randomSum) # true