我希望实现一个具有指定时间段的简单pseudorandom number generator(PRNG),并保证在该时间段内没有冲突。在做了一些研究之后,我遇到了非常着名的LCG,这是完美的。问题是,我无法理解如何正确配置它。这是我目前的实施:
function LCG (state)
{
var a = ?;
var c = ?;
var m = ?;
return (a * state + c) % m;
}
它表示,为了使所有种子值都有一个完整的时间段,必须满足以下条件:
1 和 3 很容易理解和测试。然而, 2 ,我不太明白这意味着什么或如何检查它。那么C,它可以为零吗?如果它不为零怎么办?
总的来说,我需要选择A,C和M,使得我的周期 48 ^ 5 - 1 。 M等于句号,我不确定A和C.
答案 0 :(得分:12)
来自维基百科:
如果 c 非零,则LCG将为所有种子值设置一个完整的句点,当且仅当:
- c 和 m 是相对素数,
- a -1可被 m 的所有素数因子整除,
- a -1如果 m 是4的倍数,则为4的倍数。
醇>
你说你想要一个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。
让我们来看看每一个条件:
总结:
这是一个较小的测试用例(在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