Наиболее переносимая версия, IMO, будет LCG с периодом, равным натуральному размеру слова машины.Он использует переполнение регистра для модуля, что делает его еще быстрее.Для этого нужно использовать типы данных NumPy, здесь приведен простой код, константы a, c взяты из таблицы 4 здесь
import numpy as np
def LCG(seed: np.uint64, a: np.uint64, c: np.uint64) -> np.uint64:
with np.errstate(over='ignore'):
while True:
seed = (a * seed + c)
yield seed
a = np.uint64(2862933555777941757)
c = np.uint64(1)
rng64 = LCG(np.uint64(17), a, c)
print(next(rng64))
print(next(rng64))
print(next(rng64))
И для Linux x64, и для Windows x64, а также для ОСX VM работает точно так же.
Что касается воспроизводимости, единственным преимуществом является сохранение первых чисел пары и сравнение их с выводом LCG на этапе инициализации приложения - если они в порядке, вы продолжаете.
Еще одной особенностью LCG, которая мне нравится, является ее способность прыгать вперед в журнале 2 (N) времени, где N - количество пропусков.Я мог бы предоставить вам код для этого.Используя скачок вперед, вы можете обеспечить неперекрывающиеся последовательности для параллельных независимых случайных потоков
UPDATE
Вот перевод моего кода C на Python / NumPy, похоже, работает.В логарифмическом времени он может пропускать как вперёд, так и назад.
import numpy as np
class LCG(object):
UZERO: np.uint64 = np.uint64(0)
UONE : np.uint64 = np.uint64(1)
def __init__(self, seed: np.uint64, a: np.uint64, c: np.uint64) -> None:
self._seed: np.uint64 = np.uint64(seed)
self._a : np.uint64 = np.uint64(a)
self._c : np.uint64 = np.uint64(c)
def next(self) -> np.uint64:
self._seed = self._a * self._seed + self._c
return self._seed
def seed(self) -> np.uint64:
return self._seed
def set_seed(self, seed: np.uint64) -> np.uint64:
self._seed = seed
def skip(self, ns: np.int64) -> None:
"""
Signed argument - skip forward as well as backward
The algorithm here to determine the parameters used to skip ahead is
described in the paper F. Brown, "Random Number Generation with Arbitrary Stride,"
Trans. Am. Nucl. Soc. (Nov. 1994). This algorithm is able to skip ahead in
O(log2(N)) operations instead of O(N). It computes parameters
A and C which can then be used to find x_N = A*x_0 + C mod 2^M.
"""
nskip: np.uint64 = np.uint64(ns)
a: np.uint64 = self._a
c: np.uint64 = self._c
a_next: np.uint64 = LCG.UONE
c_next: np.uint64 = LCG.UZERO
while nskip > LCG.UZERO:
if (nskip & LCG.UONE) != LCG.UZERO:
a_next = a_next * a
c_next = c_next * a + c
c = (a + LCG.UONE) * c
a = a * a
nskip = nskip >> LCG.UONE
self._seed = a_next * self._seed + c_next
np.seterr(over='ignore')
a = np.uint64(2862933555777941757)
c = np.uint64(1)
seed = np.uint64(1)
rng64 = LCG(seed, a, c) # initialization
print(rng64.next())
print(rng64.next())
print(rng64.next())
rng64.skip(-3) # back by 3
print(rng64.next())
print(rng64.next())
print(rng64.next())
rng64.skip(-3) # back by 3
rng64.skip(2) # forward by 2
print(rng64.next())
В любом случае, краткий обзор ГСЧ:
- С хорошими константами (см. Ссылку на статью L'Ecuyer)он будет охватывать весь диапазон [0 ... 2 64 ) без повторения.В основном идеальное отображение [0 ... 2 64 ) -> [0 ... 2 64 ), вы можете установить 0,1,2,3, ... в качестве входных данныхи получить вывод всего диапазона
- Это обратимо, вы можете получить предыдущее начальное число, так что отображение фактически является биекцией, [0 ... 2 64 ) <-> [0 ... 2* +1032 * 64 * * тысяча тридцать три).Подробнее см. Обратимый генератор псевдослучайных последовательностей
- . Он имеет логарифмический пропуск вперед и назад, поэтому нет проблем с поиском подходящих интервалов для параллельных вычислений - начните с одного начального числа, а затем затемпоток будет пропущен (seed, 2 64 / N), следующий поток пропущен (seed, 2 64 / N * 2) и так далее и так далее.Гарантированно не перекрываются
- Это просто и быстро, хотя и не очень высокого качества RNG