Пермутируемый конгруэнтный генератор ( PCG ) — это алгоритм генерации псевдослучайных чисел, разработанный в 2014 году доктором ME O'Neill, который применяет выходную функцию перестановки для улучшения статистических свойств линейного конгруэнтного генератора по модулю 2 n . Он достигает превосходной статистической производительности [1] [2] [3] [4] с небольшим и быстрым кодом и малым размером состояния. [5]
PCG отличается от классического линейного конгруэнтного генератора (LCG) тремя особенностями:
Именно переменное вращение устраняет проблему короткого периода в младших битах, от которой страдают LCG степени 2. [5] : 31–34
Семейство PCG включает ряд вариантов. Основной LCG определен для ширины от 8 до 128 бит [ требуется ссылка ] , хотя для практического использования рекомендуются только 64 и 128 бит; меньшие размеры предназначены для статистических тестов техники.
Аддитивная константа в LCG может изменяться для создания различных потоков. Константа представляет собой произвольное нечетное целое число, [6], поэтому ее не нужно хранить явно; можно использовать адрес самой переменной состояния (с установленным младшим битом).
Определено несколько различных выходных преобразований. Все они работают хорошо, но некоторые имеют больший запас, чем другие. [5] : 39 Они построены из следующих компонентов:
x ^= x >> constant
. Константа выбирается равной половине битов, не отброшенных следующей операцией (округленных в меньшую сторону).Они объединены в следующие рекомендуемые выходные преобразования, проиллюстрированные здесь в наиболее распространенных размерах:
count = (int)(x >> 59); x ^= x >> 18; return rotr32((uint32_t)(x >> 27), count);
.count = (int)(x >> 61); x ^= x >> 22; return (uint32_t)(x >> (29 - count));
.count = (int)(x >> 122); x64 = (uint64_t)(x ^ (x >> 64)); return rotr64(x64, count);
count=(int)(x >> 28); x ^= x >> (4 + count); x *= 277803737u; return x ^ (x >> 22);
count=(int)(x >> 59); x ^= x >> (5 + count); x *= 12605985483714917081u; return x ^ (x >> 43);
count = (int)(x >> 122); low64 = rotr64((uint64_t)(x ^ (x >> 64)), count); high64 = rotr64((uint64_t)(x >> 64), low64 & 63); return (uint128_t)high64 << 64 | low64;
Каждый шаг этих выходных преобразований либо обратим (и, следовательно, один к одному ), либо является усечением, поэтому их композиция отображает одно и то же фиксированное число входных состояний в каждое выходное значение. Это сохраняет равнораспределение базового LCG.
Наконец, если требуется длина цикла больше 2 128 , генератор может быть расширен массивом подгенераторов. Один выбирается (по очереди) для добавления к выходу основного генератора, и каждый раз, когда состояние основного генератора достигает нуля, подгенераторы циклически запускаются по шаблону, который обеспечивает период, экспоненциальный по общему размеру состояния.
Генератор, рекомендуемый для большинства пользователей [5] : 43 — это PCG-XSH-RR с 64-битным состоянием и 32-битным выходом. Его можно реализовать как:
#include <stdint.h> static uint64_t state = 0x4d595df4d0f33173 ; // Или что-то зависящее от семени static uint64_t const multiplier = 6364136223846793005u ; static uint64_t const increment = 1442695040888963407u ; // Или произвольная нечетная константа статический uint32_t rotr32 ( uint32_t x , беззнаковый r ) { return x >> r | x << ( - r & 31 ); } uint32_t pcg32 ( void ) { uint64_t x = state ; беззнаковое количество = ( беззнаковое )( x >> 59 ); // 59 = 64 - 5 состояние = x * множитель + приращение ; x ^= x >> 18 ; // 18 = (64 - 27)/2 return rotr32 (( uint32_t )( x >> 27 ), count ); // 27 = 32 - 5 } void pcg32_init ( uint64_t seed ) { state = seed + increment ; ( void ) pcg32 (); }
Генератор применяет выходное преобразование к начальному состоянию, а не к конечному , чтобы увеличить доступный параллелизм на уровне инструкций и максимизировать производительность на современных суперскалярных процессорах . [5] : 43
Немного более быстрая версия устраняет приращение, сводя LCG к мультипликативному генератору ( в стиле Лемера ) с периодом всего 2 62 и использует более слабую выходную функцию XSH-RS:
static uint64_t mcg_state = 0xcafef00dd15ea5e5u ; // Должно быть нечетным static uint64_t const multiplier = 6364136223846793005u ; uint32_t pcg32_fast ( void ) { uint64_t x = mcg_state ; беззнаковое число = ( беззнаковое )( x >> 61 ); // 61 = 64 - 3 mcg_state = x * множитель ; x ^= x >> 22 ; return ( uint32_t )( x >> ( 22 + count )); // 22 = 32 - 3 - 7 } void pcg32_fast_init ( uint64_t seed ) { mcg_state = 2 * seed + 1 ; ( void ) pcg32_fast (); }
Экономия времени минимальна, так как самая дорогая операция (умножение 64×64 бит) остается, поэтому предпочтительнее использовать обычную версию, за исключением крайних случаев . Тем не менее, эта более быстрая версия также проходит статистические тесты. [4]
При выполнении на 32-битном процессоре 64×64-битное умножение должно быть реализовано с использованием трех операций умножения 32×32→64-бит. Чтобы сократить это число до двух, существуют 32-битные умножители, которые работают почти так же хорошо, как 64-битные, например, 0xf13283ad [6] , 0xffffffff0e703b65 или 0xf2fc5985.
Мелисса О'Нил предлагает тестировать PRNG, применяя статистические тесты к их уменьшенным вариантам и определяя минимальное количество внутренних битов состояния, необходимых для прохождения. [7] BigCrush от TestU01 проверяет достаточно данных, чтобы обнаружить период 2 35 , поэтому даже идеальному генератору требуется 36 бит состояния, чтобы пройти его. Некоторые очень плохие генераторы могут пройти, если им дать достаточно большое состояние; [8] прохождение, несмотря на малое состояние, является мерой качества алгоритма и показывает, насколько велик запас прочности между этим нижним пределом и размером состояния, используемым в практических приложениях.
PCG-RXS-M-XS (с 32-битным выходом) проходит BigCrush с 36 битами состояния (минимально возможный), PCG-XSH-RR ( pcg32()
выше) требует 39, а PCG-XSH-RS ( pcg32_fast()
выше) требует 49 бит состояния. Для сравнения, xorshift* , одна из лучших альтернатив, требует 40 бит состояния, [5] : 19 и Mersenne twister терпит неудачу, несмотря на 19937 бит состояния. [9]
Было показано, что практически возможно (с большим объемом вычислений) восстановить начальное число псевдослучайного генератора, имея 512 последовательных выходных байтов. [10] Это означает, что практически возможно предсказать остальную часть псевдослучайного потока, имея 512 байтов.