Генерация случайных чисел или перестановок полного периода / полного цикла, аналогичных LCG, но без нечетных / четных

Я хочу сгенерировать псевдослучайные числа / перестановки, которые «занимают» полный период или полный цикл в пределах диапазона. Обычно для генерации таких последовательностей можно использовать «линейный конгруэнтный генератор» (LCG), используя такую ​​формулу, как:

X = (a*Xs+c) Mod R

Где Xs - начальное значение, X - результат, a и c - относительно простые константы, а R - максимум (диапазон).

(Под полным периодом / полным циклом я имею в виду, что константы могут быть выбраны так, чтобы любой X встречался только один раз в некоторой случайной / переставленной последовательности и находился в диапазоне от 0 до R-1 или от 1 до R).

LCG почти полностью удовлетворяет мои потребности. Проблема, с которой я сталкиваюсь с LCG, заключается в неслучайности нечетного / четного результата, то есть: для начального числа Xn результат X будет чередоваться нечетным / четным.

Вопросы:

  1. Кто-нибудь знает, как создать нечто подобное, в котором не будет чередоваться четное и нечетное?

  2. Я считаю, что можно построить «составной LCG», но у меня нет подробностей. Может кто-нибудь привести пример этого CLCG?

  3. Существуют ли альтернативные формулы, которые могут соответствовать приведенным выше деталям и ограничениям ниже?

Ограничения:

  1. Я хочу что-то, основанное на простой формуле на основе семян. то есть: чтобы получить следующее число, я предоставляю начальное число и получаю следующее «случайное число» в переставленной последовательности. В частности, я не могу использовать предварительно рассчитанные массивы. (См. Следующие пункты)
  2. Последовательность обязательно должна быть «полный период / полный цикл».
  3. Диапазон R может составлять несколько миллионов или даже 32 бит / 4 миллиарда.
  4. Расчет не должен страдать от переполнения и быть эффективным / быстрым, т. Е. Без больших экспонент или десятков умножений / делений.

  5. Последовательность не обязательно должна быть ужасно случайной или безопасной - мне не нужна криптографическая случайность (но я могу использовать ее, если возможно), просто «хорошая» случайность или кажущаяся случайность, без нечетных / четных последовательностей.

Любые мысли оценены - заранее спасибо.

ОБНОВЛЕНИЕ: в идеале переменная Range не может быть точной степенью двойки, но должна работать в любом случае.


person andora    schedule 27.08.2010    source источник
comment
Очень похожий вопрос был отправлен вчера. Возможно, это может вас заинтересовать. stackoverflow.com/questions/3572095/prng-with- регулируемый период /   -  person Dr. belisarius    schedule 27.08.2010
comment
Да, очень похоже. Спасибо за ссылку.   -  person andora    schedule 27.08.2010
comment
Почему вы создали награду? Что вы ищете, чего не может предложить решение Peter G? Если вам нужно что-то большее, вы должны это где-то указать.   -  person Fantius    schedule 10.06.2011
comment
@Fantius: Пересматривая это несколько раз, я не чувствовал, что существует достаточно элегантное / простое решение. И я не ценил возможность перестановки битов дать решение. Теперь, рассмотрев это подробно, я вижу, что это возможно. Однако сдвиг, я думаю, нет. Награда принесла дополнительную информацию (спасибо @btilly), но я все же хотел бы изучить и протестировать другие возможности, прежде чем принимать ответ.   -  person andora    schedule 24.06.2011


Ответы (6)


Простое решение. Сделайте LCG с R простым числом, несколько большим, чем желаемый диапазон, и a и c где-нибудь случайным образом в этом диапазоне. Если он дает вам число, которое больше, чем вы хотите, повторите итерацию еще раз, пока вы не вернетесь в диапазон.

Выходные числа не будут иметь особенно простого шаблона mod 2, 3, 5 и т. Д. Вплоть до любого простого числа, меньшего, чем используемое вами простое число.

Если диапазон, который вам нужен, велик, то ближайшее большее простое число будет лишь немного больше, поэтому вам очень редко придется вызывать его дважды. Например, если желаемый диапазон составляет миллиард, вы можете использовать 1000000007 в качестве простого числа, и вам нужно будет пропускать лишнее число менее 0,000001% времени.

Я нашел этот прайм, перейдя на http://primes.utm.edu/curios/includes/primetest.php и вставляю числа, пока не получу простое число. Мне немного повезло. Вероятность того, что n, оканчивающееся на 1, 3, 7, 9, простое, составляет примерно 2.5/log(n), из которых миллиард составляет около 12%, так что мне несколько повезло, что я нашел это число всего после 4 попыток. Но не повезло - я нашел его за 3 попытки, а в среднем мне должно было понадобиться 8.

РЕДАКТИРОВАТЬ: этот генератор случайных чисел может иметь более короткий цикл. См. Примечание @dzugaru.

person btilly    schedule 10.06.2011
comment
Небольшой пример (x = 3x + 2 mod 7) соглашается, почему OP вообще столкнулся с чередованием четных / нечетных? - person Peter G.; 11.06.2011
comment
@Peter G .: OP использовал R, который является четным, вероятно, степенью 2. Если R четный, вы получите мод повторения (2). - person btilly; 11.06.2011
comment
Спасибо, теперь понятно. Каким-то образом мой разум был заблокирован, чтобы работать с этим простым объяснением. - person Peter G.; 12.06.2011
comment
Спасибо за этот ответ. Не слишком увлечен чуть большим простым числом с возможностью множественных вызовов для попадания в диапазон (даже ты, на практике, вероятно, не нужен), но попробую и увидим. В настоящее время я разделен между LFSR / Bit Shifting / XORshiftRNG и this. Рад сообщить, что все похоже, что они могут решить мою проблему. +1 Спасибо. - person andora; 24.06.2011
comment
На самом деле вы не можете этого сделать, потому что простое число m и произвольные a и c не удовлетворяют теореме Халла-Добелла, предположительно правилу 2. Попробуйте a = 2, c = 2, m = 5, период равен m - 1, а не m . См. Этот разговор en.wikipedia.org/wiki/ - person Dzugaru; 21.06.2015
comment
Самое простое решение, которое я придумал, - это взять m некоторую степень 2, превышающую желаемый диапазон, взять c нечетное (относительно простое с m) и взять a = 1 (mod 4). В худшем случае вам придется вычислять lcg преобразование дважды, если желаемый диапазон немного выше предыдущей степени 2, но я думаю, это должно быть нормально. - person Dzugaru; 21.06.2015
comment
@btilly pinging, чтобы вы это увидели - person Dzugaru; 21.06.2015

Если ваша единственная проблема - чередование нечетных и четных, оставьте вычисление изменения состояния без изменений. Перед использованием каждого выхода вы можете либо сдвинуть младшие биты, либо поменять местами биты.

Редактировать:

С вариантом с перестановкой битов (фиксированный шаблон) вы продолжите генерировать целые периоды.

Псевдокод исходной LCG:

function rand
   state := update(state)
   return state

Псевдокод LCG, включая свопинг:

function rand2
   state := update(state) -- unchanged state computation
   return swapped(state)  -- output swapped state
person Peter G.    schedule 27.08.2010
comment
Возможный! Достаточно просто и легко, попробую. Спасибо. - person andora; 27.08.2010
comment
Я думаю, что для достижения случайности просто сдвига, ваша величина сдвига должна быть случайной. Итак, вам понадобятся два «развязанных» генератора случайных чисел. - person Dr. belisarius; 27.08.2010
comment
Не уверен, что это разумно, поскольку я, вероятно, потеряю свойство перестановки «полного периода» последовательности. Создание последовательности полного периода - жесткое ограничение. - person andora; 27.08.2010
comment
Да, возможно. Просто поменяйте местами две половины вывода LCG, чтобы сформировать новые результаты, как предлагает Питер Джи. - person Fantius; 10.06.2011
comment
Не уверен, что понимаю первое предложение: вычисление изменения состояния не изменилось? Теперь я вижу, что процесс перестановки битов в любом случае сохранит свойство полного цикла. Спасибо за помощь +1. - person andora; 24.06.2011

Кажется, что перестановочные конгруэнтные генераторы обладают всеми качествами, которые вам нужны:

http://www.pcg-random.org

// *Really* minimal PCG32 code / (c) 2014 M.E. O'Neill / pcg-random.org
// Licensed under Apache License 2.0 (NO WARRANTY, etc. see website)

typedef struct { uint64_t state;  uint64_t inc; } pcg32_random_t;

uint32_t pcg32_random_r(pcg32_random_t* rng)
{
    uint64_t oldstate = rng->state;
    // Advance internal state
    rng->state = oldstate * 6364136223846793005ULL + (rng->inc|1);
    // Calculate output function (XSH RR), uses old state for max ILP
    uint32_t xorshifted = ((oldstate >> 18u) ^ oldstate) >> 27u;
    uint32_t rot = oldstate >> 59u;
    return (xorshifted >> rot) | (xorshifted << ((-rot) & 31));
}

На веб-сайте доступны и другие варианты, в том числе реализация C ++, предназначенная для работы с заголовком <random> (например, дистрибутивы), более полная реализация C и реализация Haskell.

person bames53    schedule 03.03.2015

Еще один простой, эффективный и хорошо понятный ГПСЧ - это регистр сдвига линейной обратной связи. Полный период легко достичь, выполнив действия, описанные в статье.

РЕДАКТИРОВАТЬ:

Вы можете рассмотреть некоторые методы, разработанные для шифрования с сохранением формата. Я считаю, что их можно легко адаптировать для генерации перестановки.

person President James K. Polk    schedule 27.08.2010
comment
Раньше я рассматривал LFSR или даже LFSR Фибоначчи, но по какой-то причине, о которой я сейчас забыл, сделал скидку. Ответ Велисария на вопрос SO, связанный здесь, предлагает способ борьбы с ограничениями двоичного диапазона, поэтому я взгляну еще раз. Единственное, знаете ли вы, что он точно решает проблему нечетных / четных? Другая проблема - это «зависание», но я думаю, что это действительно имеет значение, только если реализовано оборудование. Спасибо за предложение. - person andora; 27.08.2010
comment
@andora Ага ... не четно-нечетная регулярность для фибоначчи (см. пример википедии, связанный с моим ответом на другой вопрос) - person Dr. belisarius; 28.08.2010
comment
@andorra: возможно, вы возражаете, потому что период должен иметь вид 2 ** n - 1? - person President James K. Polk; 28.08.2010
comment
Сортировка: я действительно хочу, чтобы свойство «полного цикла» сохранялось, поскольку я хочу сгенерировать набор перестановок. Обрезка результатов для достижения недвоичного диапазона проблематична. Например: предположим, я хочу «переставить» от 1 до 9, чтобы получить, скажем, 5,1,8,2,3,6,9,4,7. С двоичным диапазоном 15/16 я должен повторять вызовы, пока результат не попадет в интервал. Здесь нет большой проблемы, но для большого диапазона, скажем, 32-битного / 4-битного потенциально может быть много тысяч вызовов, пока результат не попадет в диапазон. - person andora; 28.08.2010

То, что вам не нужна криптографическая стойкость, не означает, что вы не можете заимствовать некоторые идеи из криптографии ... Например, сеть Фейстеля (конструкция Луби-Рэкоффа).

Википедия довольно ясна.

Если вы выберете простой и быстрый F - он даже не должен гарантировать уникальность вывода - тогда вы можете просто скормить последовательность (0, 1, 2, ..., 2 ^ n-1) на несколько раундов сеть Фейстеля. Поскольку конструкция обратима, это гарантирует, что результат никогда не повторится.

Пример кода для 32 бит:

#include <stdint.h>
#include <stdio.h>

/* Just some fixed "random" bits... */
union magic {
    double d;
    uint16_t n[4];
};

const union magic bits = { 3.141592653589793238462643383 };

static uint16_t
F(uint16_t k, uint16_t x)
{
    return 12345*x + k;
}

static uint32_t
gen_rand(uint32_t n)
{
    uint16_t left = n >> 16;
    uint16_t right = n & ((1UL << 16) - 1);

    for (unsigned round=0 ; round < 4 ; ++round) {
        const uint16_t next_right = left ^ F(bits.n[round], right);
        const uint16_t next_left = right;
        right = next_right;
        left = next_left;
    }

    return (((uint32_t)left) << 16) + right;
}

int
main(int argc, char *argv[])
{
    for (uint32_t n = 0 ; n < 10 ; ++n) {
        printf("gen_rand(%lu) == %08lx\n", (unsigned long)n,
               (unsigned long)gen_rand(n));
    }
    return 0;
}

Вы можете повозиться с определением F (), количеством раундов и т. Д. На свой вкус. Свойство «полного цикла» гарантируется независимо от того, что вы там используете. Другими словами, если у вас есть цикл в main перейти от 0 до 2 ^ 32-1, каждое 32-битное целое число будет встречаться один и только один раз, независимо от того, что вы используете для F или количества раундов.

Это не совсем соответствует заявленному вами требованию, поскольку вход gen_rand не является «текущим случайным числом» ... Вводом является просто следующее целое число. Однако это позволяет вам произвольно генерировать любой элемент последовательности (произвольный доступ). И это легко инвертировать, если вы действительно хотите передать «текущее случайное число» в качестве входных данных.

Довольно легко адаптироваться к разному количеству битов, хотя для этого требуется, чтобы ваш R был степенью 2.

person Nemo    schedule 15.06.2011
comment
Спасибо за этот ответ. Хотя R, вероятно, является степенью двойки, я бы предпочел, чтобы в этом не было необходимости. Я знаком с этим решением, но не решаюсь переделывать его, скажем, для 24-битного числа, когда другие решения могут быть проще для реализации. Однако исходный «ввод» не является проблемой, и мне не нужно было бы инвертировать процесс, но я понимаю, что мог бы, если бы это было необходимо. - person andora; 24.06.2011

По следующей ссылке вы можете найти пример комбинированного LCG. (Документы и источник включены) (Примечание: алгоритм открыт, но лицензия на источник не открыта (т.е. нет производного кода))

http://resource.npl.co.uk/docs/science_technology/scientific_computing/ssfm/documents/wh_rng_version096.zip

Вы даже можете попробовать этот 7-ступенчатый пример XORshift RNG:

https://gist.github.com/709285

person jj1bdx    schedule 12.12.2010
comment
Спасибо за помощь - признателен. - person andora; 24.06.2011