Выбор A, C и M для линейного конгруэнтного генератора
Я ищу, чтобы реализовать простой генератор псевдослучайных чисел (PRNG), который имеет указанный период и гарантирует отсутствие коллизий в течение этого периода. После некоторых исследований я наткнулся на очень известный LCG что идеально. Проблема в том, что у меня возникли проблемы с пониманием того, как правильно настроить его. Вот моя текущая реализация:
function LCG (state)
{
var a = ?;
var c = ?;
var m = ?;
return (a * state + c) % m;
}
Он говорит, что для того, чтобы иметь полный период для всех семян ценностей должны быть выполнены следующие условия:
- c и m относительно главных
- а-1 делится на все простые делители m
- а-1 кратно 4, Если m кратно 4
1 и 3 просты для понимания и проверки. Однако как насчет 2, я не совсем поймите, что это значит и как это проверить. А как насчет C, может быть ноль? что, если она ненулевая?
в целом мне нужно выбрать A, C и M Таким образом, чтобы у меня был период 48^5 - 1. M равно периоду, я не уверен насчет A и C.
2 ответов
Из Википедии:
предусмотрено, что c Не равно нулю, LCG будет иметь полный период для всех значений семян, если и только если:
- c и m относительно главных,
- a-1 делится на все простые делители m,
- a-1 кратно 4, Если m кратно 4.
Вы сказали, что хотите период 485-1, Так что вы должны выбрать m≥485-1. Попробуем выбрать m=485-1 и посмотреть, куда это нас приведет. Условия из статьи Википедии запрещают вам выбирать c=0 если вы хотите, чтобы срок m.
обратите внимание, что 11, 47, 541 и 911 являются основными факторами 485-1, так как они все простые и 11*47*541*911 = 485-1.
давайте рассмотрим каждое из этих условий:
- на c и m чтобы быть относительно простым,c и m не должно иметь общих простых факторов. Итак, выберите любые простые числа кроме 11, 47, 541, и 911, а затем умножить их вместе, чтобы выбрать c.
- выберите a такое, что a-1 делится на все простые множители m, то есть,a = x*11*47*541*911 + 1 для любого x по вашему выбору.
- код m не кратно 4, поэтому вы можете игнорировать третье условие.
в итоге:
- m = 485-1,
- c = любое произведение простых чисел, кроме 11, 47, 541 и 911 (также,c должно быть меньше чем m),
- a = x*11*47*541*911 + 1, для любого неотрицательного x по вашему выбору (также,a должно быть меньше m).
вот меньший тестовый случай (в Python) с использованием периода 482-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, дайте мне знать, и я могу перевести его на Яваскрипт.)
на основе ответа снежка и комментариев я создал полный пример. Вы можете использовать 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