Это продолжение ранее опубликованного вопроса:
Как сгенерировать случайное число на C?
Я хочу иметь возможность генерировать случайное число из определенного диапазона, например от 1 до 6, чтобы имитировать стороны игральной кости.
Как бы я это сделал?
Ответы:
Все ответы пока математически неверны. Возврат
rand() % N
не дает единообразного числа в диапазоне,[0, N)
если неN
делит длину интервала, на которыйrand()
выполняется возврат (т.е. является степенью 2). Более того, никто не знает, независимы ли модулиrand()
: возможно, что они идут0, 1, 2, ...
, что равномерно, но не очень случайно. Единственное предположение, которое кажется разумным, состоит в томrand()
, что выводится распределение Пуассона: любые два неперекрывающихся подинтервала одинакового размера одинаково вероятны и независимы. Для конечного набора значений это подразумевает равномерное распределение, а также гарантирует, что значенияrand()
хорошо разбросаны.Это означает, что единственный правильный способ изменить диапазон
rand()
- разделить его на блоки; например, еслиRAND_MAX == 11
вам нужен диапазон1..6
, вам следует присвоить{0,1}
1,{2,3}
2 и так далее. Это непересекающиеся интервалы одинакового размера, поэтому они распределены равномерно и независимо.Предложение использовать деление с плавающей запятой математически правдоподобно, но в принципе страдает проблемами округления. Возможно,
double
это достаточно высокая точность, чтобы заставить его работать; возможно нет. Я не знаю и не хочу разбираться в этом; в любом случае ответ зависит от системы.Правильный способ - использовать целочисленную арифметику. То есть вам нужно что-то вроде следующего:
Петля необходима для получения идеально равномерного распределения. Например, если вам заданы случайные числа от 0 до 2, а вам нужны только числа от 0 до 1, вы просто продолжаете тянуть, пока не получите 2; нетрудно проверить, что это дает 0 или 1 с равной вероятностью. Этот метод также описан в ссылке, приведенной в ответе №№, но с другим кодом. Я использую,
random()
а неrand()
потому, что он имеет лучшее распределение (как указано на странице руководства дляrand()
).Если вы хотите получить случайные значения за пределами диапазона по умолчанию
[0, RAND_MAX]
, вам придется сделать что-то сложное. Пожалуй, наиболее целесообразным является определить функцию ,random_extended()
которая тянетn
бит ( с использованиемrandom_at_most()
) и возвращается в[0, 2**n)
, а затем применитьrandom_at_most()
сrandom_extended()
вместоrandom()
(и2**n - 1
вместоRAND_MAX
) , чтобы тянуть случайное значение меньше2**n
, если у вас есть числовой тип , который может содержать такие ценность. Наконец, конечно, вы можете получать значения при[min, max]
использованииmin + random_at_most(max - min)
, включая отрицательные значения.источник
max - min > RAND_MAX
, что более серьезно, чем проблема, о которой я говорил выше (например, VC ++ имеетRAND_MAX
только 32767).do {} while()
.Следуя ответу @Ryan Reich, я подумал, что предлагаю свою очищенную версию. Первая проверка границ не требуется, учитывая вторую проверку границ, и я сделал ее итеративной, а не рекурсивной. Он возвращает значения в диапазоне [min, max], где
max >= min
и1+max-min < RAND_MAX
.источник
limit
int (и, возможно,bucket
тоже), посколькуRAND_MAX / range
<INT_MAX
иbuckets * range
<=RAND_MAX
. РЕДАКТИРОВАТЬ: я отправил и отредактировал предложение.Вот формула, если вам известны максимальное и минимальное значения диапазона и вы хотите сгенерировать числа, включительно между диапазоном:
источник
int
переполнениеmax+1-min
.Смотрите здесь другие варианты.
источник
(((max-min+1)*rand())/RAND_MAX)+min
и, вероятно, получить точно такое же распределение (при условии, что RAND_MAX достаточно мал относительно int, чтобы не переполняться).max + 1
, если оно естьrand() == RAND_MAX
, илиrand()
очень близко к нему,RAND_MAX
а ошибки с плавающей запятой вытесняют окончательный результатmax + 1
. Чтобы быть в безопасности, вы должны убедиться, что результат находится в пределах допустимого диапазона, прежде чем возвращать его.RAND_MAX + 1.0
. Я все еще не уверен, что этого достаточно, чтобы предотвратитьmax + 1
возврат: в частности,+ min
в конце есть раунд, который может привестиmax + 1
к большим значениям rand (). Безопаснее вообще отказаться от этого подхода и использовать целочисленную арифметику.RAND_MAX
заменяетсяRAND_MAX+1.0
как предполагает Christoph, то я считаю , что это безопасно при условии , что+ min
это делается с использованием целочисленной арифметики:return (unsigned int)((max - min + 1) * scaled) + min
. Причина (неочевидная) состоит в том, что если предположить арифметику IEEE 754 и округление до половины (а также этоmax - min + 1
точно может быть представлено как двойное, но это будет верно на типичной машине), всегда верно, чтоx * scaled < x
для любой положительный дубльx
и любой двойнойscaled
удовлетворительный0.0 <= scaled && scaled < 1.0
.randr(0, UINT_MAX)
: всегда генерирует 0.Не могли бы вы просто сделать:
%
- оператор модуля. По сути, он просто разделится на 6 и вернет остаток ... от 0 до 5.источник
rand()
включает младшие биты состояния генератора (если он использует LCG). Я пока не видел ни одного - все они (да, включая MSVC с RAND_MAX, равным 32767) удаляют младшие биты. Использование модуля не рекомендуется по другим причинам, а именно из-за того, что он искажает распределение в пользу меньших чисел.Для тех, кто понимает проблему смещения, но не переносит непредсказуемое время работы методов, основанных на отклонении, эта серия дает постепенно менее смещенное случайное целое число в
[0, n-1]
интервале:Это достигается путем синтеза высокоточного случайного числа
i * log_2(RAND_MAX + 1)
битов с фиксированной точкой (гдеi
- количество итераций) и выполнения длинного умножения наn
.Когда количество битов достаточно велико по сравнению с
n
, смещение становится неизмеримо малым.Не имеет значения,
RAND_MAX + 1
меньше ли оноn
(как в этом вопросе ) или не является степенью двойки, но следует проявлять осторожность, чтобы избежать целочисленного переполнения, еслиRAND_MAX * n
оно велико.источник
RAND_MAX
частоINT_MAX
, поэтомуRAND_MAX + 1
-> UB (как INT_MIN)RAND_MAX * n
оно велико". Вам необходимо организовать использование соответствующих типов в соответствии с вашими требованиями.RAND_MAX
частоINT_MAX
" да, но только в 16-битных системах! Любая разумно современная архитектура поставитINT_MAX
2 ^ 32/2 иRAND_MAX
2 ^ 16/2. Это неверное предположение?int
компилятора, нашелRAND_MAX == 32767
на одном иRAND_MAX == 2147483647
на другом. Мой общий опыт (десятилетия)RAND_MAX == INT_MAX
таков чаще. Так не согласен , что достаточно современный 32-битная архитектура, безусловно , естьRAND_MAX
в2^16 / 2
. Поскольку спецификация C позволяет32767 <= RAND_MAX <= INT_MAX
, я все равно кодирую это, а не тенденцию.Чтобы избежать смещения по модулю (предложенного в других ответах), вы всегда можете использовать:
Где «MAX» - это верхняя граница, а «MIN» - нижняя граница. Например, для чисел от 10 до 20:
Простое решение и лучше, чем использование "rand ()% N".
источник
#include <bsd/stdlib.h>
сначала. Кроме того, есть идеи, как получить это в Windows без MinGW или CygWin?Вот несколько более простой алгоритм, чем решение Райана Райха:
источник
RAND_MAX + 1
может легко переполнитьint
добавление. В этом случае(RAND_MAX + 1) % range
будут получены сомнительные результаты. Подумайте(RAND_MAX + (uint32_t)1)
Хотя Райан прав, решение может быть намного проще в зависимости от того, что известно об источнике случайности. Чтобы переформулировать проблему:
[0, MAX)
с равномерным распределением.[rmin, rmax]
где0 <= rmin < rmax < MAX
.По моему опыту, если количество бункеров (или «ящиков») значительно меньше диапазона исходных чисел, а исходный источник криптографически надежен - нет необходимости повторять всю эту ригамаролу, и простое деление по модулю будет достаточно (например
output = rnd.next() % (rmax+1)
, еслиrmin == 0
) и производят случайные числа, которые распределяются равномерно "достаточно" и без какой-либо потери скорости. Ключевым фактором является источник случайности (например, дети, не пытайтесь делать это домаrand()
).Вот пример / доказательство того, как это работает на практике. Я хотел генерировать случайные числа от 1 до 22, имея криптографически надежный источник, который генерирует случайные байты (на основе Intel RDRAND). Результат:
Это настолько близко к единообразию, насколько мне нужно для моей цели (справедливый бросок костей, создание криптографически стойких кодовых книг для шифровальных машин Второй мировой войны, таких как http://users.telenet.be/d.rijmenants/en/kl-7sim.htm и т. Д. ). Вывод не показывает заметной предвзятости.
Вот источник криптографически стойкого (истинного) генератора случайных чисел: Intel Digital Random Number Generator и образец кода, который производит 64-битные (беззнаковые) случайные числа.
Я скомпилировал его на Mac OS X с clang-6.0.1 (прямо) и с gcc-4.8.3 с использованием флага «-Wa, q» (поскольку GAS не поддерживает эти новые инструкции).
источник
gcc randu.c -o randu -Wa,q
(GCC 5.3.1 в Ubuntu 16) илиclang randu.c -o randu
(Clang 3.8.0) работает, но выгружает ядро во время выполнения сIllegal instruction (core dumped)
. Любые идеи?rand()
. Я попробовал несколько тестов и опубликовал этот вопрос, но пока не могу найти окончательного ответа.Как было сказано ранее, по модулю недостаточно, потому что он искажает распределение. Вот мой код, который маскирует биты и использует их, чтобы гарантировать, что распределение не искажено.
Следующий простой код позволяет вам посмотреть на распределение:
источник
v = rand(); if (v > RAND_MAX - (RAND_MAX % range) -> reject and try again; else return v % range;
я понимаю, что по модулю намного медленнее, чем маскирование, но я все же думаю ... что это нужно проверить.rand()
возвращаетint
в диапазоне[0..RAND_MAX]
. Этот диапазон легко может быть поддиапазоном,uint32_t
и тогдаrandomInRange(0, ,b)
никогда не будут генерироваться значения в этом диапазоне(INT_MAX...b]
.Вернет число с плавающей запятой в диапазоне [0,1]:
источник