Так rand()
же как и генератор псевдослучайных чисел, который выбирает натуральное число между 0 и RAND_MAX
является константой, определенной в cstdlib
(см. Эту статью для общего обзораrand()
).
Что произойдет, если вы захотите сгенерировать случайное число, скажем, между 0 и 2? Для объяснения, скажем RAND_MAX
, 10, и я решил сгенерировать случайное число от 0 до 2, позвонив rand()%3
. Тем rand()%3
не менее, не производит числа между 0 и 2 с равной вероятностью!
Когда rand()
возвращается 0, 3, 6, или 9, rand()%3 == 0
. Следовательно, P (0) = 4/11
Когда rand()
возвращается 1, 4, 7 или 10 rand()%3 == 1
,. Следовательно, P (1) = 4/11
Когда rand()
возвращается 2, 5 или 8 rand()%3 == 2
,. Следовательно, P (2) = 3/11
Это не генерирует числа между 0 и 2 с равной вероятностью. Конечно, для небольших диапазонов это может быть не самой большой проблемой, но для большего диапазона это может исказить распределение, смещая меньшие числа.
Так когда же rand()%n
возвращается диапазон чисел от 0 до n-1 с равной вероятностью? Когда RAND_MAX%n == n - 1
. В этом случае, наряду с нашим более ранним предположением rand()
, возвращает число между 0 и RAND_MAX
с равной вероятностью, классы по модулю n также будут равномерно распределены.
Итак, как мы решаем эту проблему? Грубо говоря, продолжать генерировать случайные числа, пока вы не получите число в нужном диапазоне:
int x;
do {
x = rand();
} while (x >= n);
но это неэффективно для низких значений n
, поскольку у вас есть только n/RAND_MAX
шанс получить значение в вашем диапазоне, и поэтому вам нужно будет выполнять RAND_MAX/n
вызовы в rand()
среднем.
Более эффективный подход на основе формул состоял бы в том, чтобы взять некоторый большой диапазон с длиной, кратной n
, например RAND_MAX - RAND_MAX % n
, продолжать генерировать случайные числа до тех пор, пока вы не получите значение, лежащее в диапазоне, а затем взять модуль:
int x;
do {
x = rand();
} while (x >= (RAND_MAX - RAND_MAX % n));
x %= n;
Для небольших значений n
это редко потребует более одного вызова rand()
.
Работы цитируются и читаем дальше:
RAND_MAX%n == n - 1
_ _ есть(RAND_MAX + 1) % n == 0
. При чтении кода я склонен понимать его% something == 0
как «равномерно делимый» с большей готовностью, чем другие способы его вычисления. Конечно, если ваш C ++ stdlib имеетRAND_MAX
то же значение, что иINT_MAX
,(RAND_MAX + 1)
конечно, не будет работать; поэтому расчет Марка остается самой безопасной реализацией.Продолжайте выбирать случайное число - это хороший способ убрать смещение.
Обновить
Мы могли бы сделать код быстрым, если бы мы искали x в диапазоне, кратном
n
.Вышеуказанный цикл должен быть очень быстрым, скажем, в среднем за 1 итерацию.
источник
rand()
возвращаемых значений не кратноn
, то, что бы вы ни делали, вы неизбежно получите «смещение по модулю», если только вы не отбросите некоторые из этих значений. user1413793 объясняет это приятно (хотя решение, предложенное в этом ответе, действительно отвратительно).RAND_MAX+1 - (RAND_MAX+1) % n
работать правильно, но я все же думаю, что это должно быть написаноRAND_MAX+1 - ((RAND_MAX+1) % n)
для ясности.RAND_MAX == INT_MAX
(как это происходит в большинстве систем) . Смотрите мой второй комментарий к @ user1413793 выше.@ user1413793 правильно о проблеме. Я не буду обсуждать это дальше, за исключением одного замечания: да, для малых значений
n
и больших значенийRAND_MAX
смещение по модулю может быть очень маленьким. Но использование шаблона смещения означает, что вы должны учитывать смещение каждый раз, когда вычисляете случайное число и выбираете разные шаблоны для разных случаев. И если вы сделаете неправильный выбор, ошибки, которые он вносит, неуловимы и почти невозможны для модульного тестирования. По сравнению с использованием только соответствующего инструмента (такого какarc4random_uniform
), это дополнительная работа, а не меньшая. Выполнение большей работы и получение худшего решения - это ужасная разработка, особенно если делать это правильно каждый раз легко на большинстве платформ.К сожалению, реализации решения все неверны или менее эффективны, чем должны быть. (Каждое решение имеет различные комментарии, объясняющие проблемы, но ни одно из решений не было исправлено для их решения.) Это может сбить с толку случайного ищущего ответа, поэтому я предоставляю здесь заведомо хорошую реализацию.
Опять же, лучшее решение - это просто использовать
arc4random_uniform
на платформах, которые его предоставляют, или аналогичное решение для вашей платформы (например,Random.nextInt
на Java). Он будет делать правильные вещи без затрат на код. Это почти всегда правильный звонок.Если у вас его нет
arc4random_uniform
, то вы можете использовать возможности open source, чтобы точно увидеть, как он реализован поверх более широкого диапазона ГСЧ (ar4random
в данном случае, но аналогичный подход может также работать поверх других ГСЧ).Вот реализация OpenBSD :
Стоит отметить последний комментарий коммита по этому коду для тех, кому нужно реализовать похожие вещи:
Реализация Java также легко доступна (см. Предыдущую ссылку):
источник
arcfour_random()
самом деле использовать настоящий алгоритм RC4 в своей реализации, выходные данные определенно будут иметь некоторые смещения. Надеемся, что авторы вашей библиотеки переключились на использование лучшего CSPRNG за тем же интерфейсом. Я помню, что одна из BSD теперь фактически использует алгоритм ChaCha20 для реализацииarcfour_random()
. Еще на выходные уклонах RC4 , которые делают его бесполезным для безопасности или других критически важных приложений , таких как видео - покер: blog.cryptographyengineering.com/2013/03/.../dev/random
прошлом также использовал RC4 на некоторых платформах (Linux использует SHA-1 в режиме счетчика). К сожалению, справочные страницы, которые я нашел с помощью поиска, показывают, что RC4 все еще используется на различных платформах, которые предлагаютarc4random
(хотя реальный код может отличаться).-upper_bound % upper_bound == 0
??-upper_bound % upper_bound
действительно будет 0, еслиint
он шире 32-битного. Так и должно быть(u_int32_t)-upper_bound % upper_bound)
(при условии,u_int32_t
что это BSD-измuint32_t
).Определение
Смещение по модулю является внутренним смещением при использовании арифметики по модулю, чтобы уменьшить выходной набор до подмножества входного набора. В общем случае, смещение существует всякий раз, когда отображение между входным и выходным набором распределяется неравномерно, как в случае использования арифметики по модулю, когда размер выходного набора не является делителем размера входного набора.
Этого смещения особенно трудно избежать в вычислениях, где числа представлены в виде цепочек битов: 0 и 1. Найти действительно случайные источники случайности также чрезвычайно сложно, но это выходит за рамки этого обсуждения. В оставшейся части этого ответа предположим, что существует неограниченный источник действительно случайных битов.
Пример задачи
Давайте рассмотрим моделирование броска кубика (от 0 до 5) с использованием этих случайных битов. Есть 6 возможностей, поэтому нам нужно достаточно бит для представления числа 6, которое составляет 3 бита. К сожалению, 3 случайных бита дают 8 возможных результатов:
Мы можем уменьшить размер набора результатов ровно до 6, взяв значение по модулю 6, однако это представляет проблему смещения по модулю :
110
дает 0 и111
1. Этот кубик загружается.Потенциальные решения
Подход 0:
Вместо того, чтобы полагаться на случайные биты, теоретически можно нанять небольшую армию, чтобы бросать кости весь день и записывать результаты в базу данных, а затем использовать каждый результат только один раз. Это примерно так же практично, как кажется, и, скорее всего, не даст действительно случайных результатов в любом случае (каламбур).
Подход 1:
Вместо того чтобы использовать модуль, наивный , но математически правильное решение , чтобы отменить результаты , что выход
110
и111
и просто попробовать еще раз с 3 - мя новыми битами. К сожалению, это означает, что есть на каждый бросок с вероятностью 25% потребуется повторный бросок, включая каждый повторный бросок . Это явно непрактично для всех, кроме самого тривиального использования.Подход 2:
Используйте больше битов: вместо 3 битов используйте 4. Это дает 16 возможных результатов. Конечно, перекатывание в любое время, когда результат больше 5, ухудшает ситуацию (10/16 = 62,5%), так что само по себе это не поможет.
Обратите внимание, что 2 * 6 = 12 <16, поэтому мы можем безопасно принять любой результат, меньший 12, и уменьшить его по модулю 6, чтобы равномерно распределить результаты. Остальные 4 результата должны быть отброшены, а затем повторно свернуты, как в предыдущем подходе.
Сначала звучит хорошо, но давайте проверим математику:
Этот результат неудачный, но давайте попробуем еще раз с 5 битами:
Определенное улучшение, но не достаточно хорошее во многих практических случаях. Хорошая новость заключается в том, что добавление большего количества битов никогда не увеличит шансы на то, что они будут выброшены и переброшены . Это верно не только для игры в кости, но и во всех случаях.
Однако, как показано , добавление 1 дополнительного бита может ничего не изменить. Фактически, если мы увеличим наш бросок до 6 битов, вероятность останется 6,25%.
Это вызывает 2 дополнительных вопроса:
Общее решение
К счастью, ответ на первый вопрос - да. Проблема с 6 состоит в том, что 2 ^ x mod 6 переворачивается между 2 и 4, которые по совпадению кратны 2 друг от друга, так что для четного x> 1,
Таким образом, 6 является скорее исключением, чем правилом. Можно найти более крупные модули, которые дают последовательные степени 2 таким же образом, но в конечном итоге это должно обернуться, и вероятность сброса будет уменьшена.
Доказательство концепции
Вот пример программы, которая использует libcrypo для OpenSSL для предоставления случайных байтов. При компиляции не забудьте указать ссылку на библиотеку, с
-lcrypto
которой большинство из них должны иметь доступ.Я призываю играть с
MODULUS
иROLLS
значениями , чтобы увидеть , сколько повторных рулоны на самом деле произошли в большинстве условий. Скептик может также пожелать сохранить вычисленные значения в файл и убедиться, что распределение выглядит нормальным.источник
randomPool = RAND_bytes(...)
Линия всегда будет приводить вrandomPool == 1
связи с утверждением. Это всегда приводит к сбросу и повторному броску. Я думаю, что вы хотели объявить в отдельной строке. Следовательно, это привело к тому, что ГСЧ возвращалось с1
каждой итерацией.randomPool
всегда будет оцениваться в1
соответствии с документациейRAND_bytes()
OpenSSL для, так как он всегда будет успешным благодаряRAND_status()
утверждению.Есть две обычные жалобы с использованием по модулю.
один действителен для всех генераторов. Это легче увидеть в предельном случае. Если ваш генератор имеет значение RAND_MAX, равное 2 (что не соответствует стандарту C), и вы хотите использовать только 0 или 1 в качестве значения, при использовании modulo будет генерироваться 0 в два раза чаще (когда генератор генерирует 0 и 2), чем будет. генерировать 1 (когда генератор генерирует 1). Обратите внимание, что это верно, как только вы не отбрасываете значения, независимо от того, какое отображение вы используете от значений генератора к требуемому, одно произойдет в два раза чаще, чем другое.
у некоторых генераторов их менее значимые биты менее случайны, чем у других, по крайней мере, для некоторых из их параметров, но, к сожалению, у этих параметров есть другая интересная характеристика (такая, что RAND_MAX может иметь единицу меньше, чем степень 2). Эта проблема хорошо известна, и в течение длительного времени реализация библиотеки, вероятно, избегала этой проблемы (например, реализация примера rand () в стандарте C использует этот тип генератора, но отбрасывает 16 менее значимых битов), но некоторые любят жаловаться на это и вам может не повезло
Используя что-то вроде
генерация случайного числа от 0 до n позволит избежать обеих проблем (и избежать переполнения с помощью RAND_MAX == INT_MAX)
Кстати, в C ++ 11 введены стандартные способы редукции и другие генераторы, кроме rand ().
источник
Решение Марка (принятое решение) почти идеально.
Тем не менее, он имеет оговорку, которая отбрасывает 1 действительный набор результатов в любом сценарии, где
RAND_MAX
(RM
) на 1 меньше, чем кратноеN
(гдеN
= количество возможных действительных результатов).т. е. когда «количество сброшенных значений» (
D
) равноN
, тогда они фактически являются допустимым набором (аV)
не недействительным набором (I
).Причиной этого является то, что в какой-то момент Марк теряет из виду разницу между
N
иRand_Max
.N
это набор действительных членов, состоящий только из положительных целых чисел, поскольку он содержит количество ответов, которые были бы действительными. (например: SetN
={1, 2, 3, ... n }
)Rand_max
Однако это набор, который (как определено для наших целей) включает любое количество неотрицательных целых чисел.В его наиболее общей форме, что определяется здесь как
Rand Max
набор всех действительных результатов, которые теоретически могут включать отрицательные числа или нечисловые значения.Поэтому
Rand_Max
лучше определить его как «Возможные ответы».Однако
N
работает против количества значений в наборе допустимых ответов, поэтому даже как определено в нашем конкретном случае,Rand_Max
будет значение на единицу меньше, чем общее число, которое он содержит.Используя решение Марка, значения отбрасываются, когда: X => RM - RM% N
Как вы можете видеть в приведенном выше примере, когда значение X (случайное число, которое мы получаем из начальной функции) равно 252, 253, 254 или 255, мы отбрасываем его, даже если эти четыре значения составляют действительный набор возвращаемых значений ,
IE: когда счетчик значений Discarded (I) = N (Количество действительных результатов), то Действительный набор возвращаемых значений будет отброшен исходной функцией.
Если мы опишем разницу между значениями N и RM как D, то есть:
Затем, когда значение D становится меньше, Процент ненужных повторных бросков из-за этого метода увеличивается при каждом естественном мультипликате. (Когда RAND_MAX НЕ равен простому числу, это имеет значение)
НАПРИМЕР:
Поскольку процент необходимых Rerolls увеличивается по мере приближения N к RM, это может иметь значение для многих различных значений в зависимости от ограничений системы, в которой он работает, и от искомых значений.
Чтобы отрицать это, мы можем внести простую поправку, как показано здесь:
Это обеспечивает более общую версию формулы, которая учитывает дополнительные особенности использования модуля для определения ваших максимальных значений.
Примеры использования небольшого значения для RAND_MAX, которое является мультипликативным для N.
Mark'original Версия:
Обобщенная версия 1:
Кроме того, в случае, когда N должно быть числом значений в RAND_MAX; в этом случае вы можете установить N = RAND_MAX +1, если только RAND_MAX = INT_MAX.
По циклу вы можете просто использовать N = 1, и любое значение X будет, тем не менее, принято и вставлять оператор IF для вашего окончательного множителя. Но, возможно, у вас есть код, который может иметь вескую причину для возврата 1, когда функция вызывается с n = 1 ...
Поэтому может быть лучше использовать 0, что обычно дает ошибку Div 0, когда вы хотите иметь n = RAND_MAX + 1
Обобщенная версия 2:
Оба эти решения решают проблему с ненужными отклоненными действительными результатами, которые произойдут, когда RM + 1 является произведением n.
Вторая версия также охватывает сценарий крайнего случая, когда вам нужно n, чтобы равняться общему возможному набору значений, содержащихся в RAND_MAX.
Модифицированный подход в обоих случаях одинаков и позволяет найти более общее решение необходимости предоставления действительных случайных чисел и минимизации отброшенных значений.
Чтобы повторить:
Основное общее решение, которое расширяет пример знака:
Расширенное общее решение, которое допускает один дополнительный сценарий RAND_MAX + 1 = n:
В некоторых языках (особенно в интерпретируемых) выполнение вычислений операции сравнения вне условия while может привести к более быстрым результатам, поскольку это однократное вычисление независимо от того, сколько повторных попыток требуется. YMMV!
источник
RAND_MAX%n = n - 1
При
RAND_MAX
значении3
(в действительности оно должно быть намного выше, чем это, но смещение все еще существует) из этих вычислений имеет смысл, что есть смещение:1 % 2 = 1
2 % 2 = 0
3 % 2 = 1
random_between(1, 3) % 2 = more likely a 1
В этом случае
% 2
вам не следует делать случайное число между0
и1
. Вы можете получить случайное число между0
и2
, тем не% 3
менее, потому что в этом случае:RAND_MAX
кратно3
.Другой метод
Существует гораздо проще, но, чтобы добавить к другим ответам, вот мое решение, чтобы получить случайное число между
0
иn - 1
, таким образом,n
разными возможностями, без смещения.>= n
, перезапустите (не по модулю).Действительно случайные данные получить нелегко, поэтому зачем использовать больше битов, чем необходимо.
Ниже приведен пример в Smalltalk, использующий кэш битов от генератора псевдослучайных чисел. Я не эксперт по безопасности, поэтому используйте на свой страх и риск.
источник
Как следует из принятого ответа , «смещение по модулю» коренится в низком значении
RAND_MAX
. Он использует чрезвычайно малое значениеRAND_MAX
(10), чтобы показать, что если бы RAND_MAX было 10, то вы пытались сгенерировать число от 0 до 2, используя%, в результате получились бы следующие результаты:Таким образом, есть 4 выхода 0 (шанс 4/10) и только 3 выхода 1 и 2 (шансы 3/10 каждый).
Так что это предвзято. Меньшие числа имеют больше шансов выйти.
Но это проявляется так очевидно, когда
RAND_MAX
мало . Или, более конкретно, когда число, на которое вы моддируете, велико по сравнению сRAND_MAX
.Гораздо лучшим решением, чем зацикливание (которое безумно неэффективно и даже не следует предлагать), является использование PRNG с гораздо большим выходным диапазоном. Твистер Мерсенн алгоритм имеет максимальную мощность 4294967295. Таким образом, выполнение
MersenneTwister::genrand_int32() % 10
всех намерений и целей будет равномерно распределено, а эффект смещения по модулю практически исчезнет.источник
MT::genrand_int32()%2
выбирает 0 (50 + 2.3e-8)% времени и 1 (50 - 2.3e-8)% времени. Если вы не строите RGN в казино (для которого вы, вероятно, использовали бы гораздо больший диапазон RGN), любой пользователь не будет замечать дополнительных 2,3–8% времени. Вы говорите о числах, слишком маленьких, чтобы иметь значение здесь.RAND_MAX
значения уменьшит смещение по модулю, но не устранит его. Цикл будет.RAND_MAX
он достаточно велик, чем число, которое вы модифицируете, то количество раз, которое вам нужно для восстановления случайного числа, исчезающе мало и не повлияет на эффективность. Я говорю, продолжайте цикл, пока вы проверяете по наибольшему кратному,n
а не так,n
как предложено принятым ответом.Я только что написал код для метода беспристрастного подбрасывания монет фон Неймана, который теоретически должен устранить любые смещения в процессе генерации случайных чисел. Дополнительную информацию можно найти по адресу ( http://en.wikipedia.org/wiki/Fair_coin )
источник
rand() % 100
100 раз. Б) если все результаты разные, возьмите первый. C) в противном случае, GOTO A. Это будет работать, но с ожидаемым числом итераций около 10 ^ 42, вам придется быть довольно терпеливым. И бессмертный.else if(prev==2) prev= x1; else { if(prev!=x1) return prev; prev=2;}