Равномерно генерировать случайные числа по всему диапазону


94

Мне нужно сгенерировать случайные числа в пределах указанного интервала [макс; мин].

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

В настоящее время я генерирую как:

for(int i=0; i<6; i++)
{
    DWORD random = rand()%(max-min+1) + min;
}

Согласно моим тестам, случайные числа генерируются только около одной точки.

Example
min = 3604607;
max = 7654607;

Генерируются случайные числа:

3631594
3609293
3630000
3628441
3636376
3621404

Из ответов ниже: ОК, RAND_MAX - 32767. Я использую платформу C ++ Windows. Есть ли другой метод генерации случайных чисел с равномерным распределением?



1
Я понятия не имел, что C ++ rand()единообразен. Какую библиотеку вы используете? cstdlib.hне rand()является единообразным: cplusplus.com/reference/cstdlib/rand
Майк Уоррен

3
Нет, rand () унифицирован (за исключением некоторых ранних реализаций с ошибками). что не является однородным, так это использование оператора модуля «%» для ограничения диапазона. См. Stackoverflow.com/questions/2999075/… для правильного решения, или если у вас есть 'arc4random_uniform', вы также можете использовать его напрямую.
Джон Мичем,

@ Alien01: Не могли бы вы изменить принятый ответ на ответ "Shoe" ("Почему ранд - плохая идея" и т. Д.)? Мой ответ действительно устарел, и каждый раз, когда я получаю за него голос, я чувствую, что кто-то идет не по тому проходу.
peterchen

Ницца технический документ о случайном выборе в С ++ 11.
Пупсик

Ответы:


158

Почему randплохая идея

Большинство ответов, которые вы здесь получили, используют randфункцию и оператор модуля. Этот метод может не генерировать числа равномерно (это зависит от диапазона и значения RAND_MAX), и поэтому не рекомендуется.

C ++ 11 и генерация в диапазоне

В C ++ 11 появилось множество других возможностей. Один из которых соответствует вашим требованиям, для генерации случайного числа в диапазоне, довольно хорошо: std::uniform_int_distribution. Вот пример:

const int range_from  = 0;
const int range_to    = 10;
std::random_device                  rand_dev;
std::mt19937                        generator(rand_dev());
std::uniform_int_distribution<int>  distr(range_from, range_to);

std::cout << distr(generator) << '\n';

А вот работающий пример.

Функция шаблона может помочь некоторым:

template<typename T>
T random(T range_from, T range_to) {
    std::random_device                  rand_dev;
    std::mt19937                        generator(rand_dev());
    std::uniform_int_distribution<T>    distr(range_from, range_to);
    return distr(generator);
}

Другие генераторы случайных чисел

В <random>Заголовок предлагает бесчисленное множество других генераторов случайных чисел с различными видами распределений , включая Бернулли, Пуассона и нормальное.

Как перетасовать контейнер?

Стандарт предусматривает std::shuffle, что можно использовать следующим образом:

std::vector<int> vec = {4, 8, 15, 16, 23, 42};

std::random_device random_dev;
std::mt19937       generator(random_dev());

std::shuffle(vec.begin(), vec.end(), generator);

Алгоритм будет переупорядочивать элементы случайным образом с линейной сложностью.

Boost.Random

Другой альтернативой, если у вас нет доступа к компилятору C ++ 11 +, является использование Boost.Random . Его интерфейс очень похож на интерфейс C ++ 11.


24
ОБРАТИТЕ ВНИМАНИЕ на этот ответ, так как он намного современнее.
gsamaras

Это правильный ответ. Благодарность! Тем не менее, я хотел бы видеть более подробное описание каждого шага этого кода. Например, что такое mt19937тип?
Apollo

@Apollo В документации написано: «32-битный Mersenne Twister Мацумото и Нисимура, 1998». Я предполагаю, что это алгоритм генерации псевдослучайных чисел.
Shoe

@Shoe, для заданного диапазона, он генерирует числа в же порядке 1 9 6 2 8 7 1 4 7 7. Вы как рандомизируете это каждый раз, когда мы запускаем программу?

1
@ Ричард Какая альтернатива?
Shoe

60

[править] Предупреждение: не используйте rand()для статистики, моделирования, криптографии или чего-либо серьезного.

Достаточно хорошо, чтобы числа выглядели случайными для обычного человека, спешащего, не более того.

См. Ответ @ Jefffrey для получения лучших вариантов или этот ответ для криптозащищенных случайных чисел.


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

((double) rand() / (RAND_MAX+1)) * (max-min+1) + min

Примечание : убедитесь, что RAND_MAX + 1 не переполняется (спасибо Деми)!

Деление генерирует случайное число в интервале [0, 1); «растянуть» это до необходимого диапазона. Только когда max-min + 1 приближается к RAND_MAX, вам понадобится функция BigRand (), как написано Марком Рэнсомом.

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


Качество встроенного генератора случайных чисел не гарантирует качества, необходимого для статистического моделирования. Это нормально, что числа «выглядят случайными» для человека, но для серьезного приложения вам следует взять что-то получше - или, по крайней мере, проверить его свойства (равномерное распределение обычно хорошо, но значения имеют тенденцию коррелировать, а последовательность детерминирована. ). У Кнута есть отличный (хотя и трудный для чтения) трактат о генераторах случайных чисел, и я недавно обнаружил, что LFSR превосходен и чертовски прост в реализации, учитывая, что его свойства вам подходят.


4
BigRand может дать лучшие результаты, даже если желаемый диапазон не превышает RAND_MAX. Рассмотрим случай, когда RAND_MAX равен 32767 и вам нужно 32767 возможных значений - два из этих 32768 случайных чисел (включая ноль) будут отображаться в один и тот же результат, и их вероятность будет в два раза выше, чем для других. Вряд ли идеальное случайное свойство!
Марк Рэнсом,

8
(RAND_MAX + 1) - плохая идея. Это может перевернуться и дать вам отрицательное значение. Лучше сделать что-нибудь вроде: ((double) RAND_MAX) + 1.0
Demi

4
@peterchen: Я думаю, вы неправильно поняли, что говорила Деми. Она имела в виду следующее: ( rand() / ((double)RAND_MAX+1)) * (max-min+1) + min просто переместите преобразование на удвоение и избегайте проблемы.
Mooing Duck

3
Кроме того, это просто изменяет распределение от нижних 32767 значений в диапазоне до 32767 равномерно распределенных значений в диапазоне, а остальные 4017233 значения никогда не будут выбраны этим алгоритмом.
Mooing Duck

2
Данный ответ отличается на 1. Правильное уравнение: ((double) rand () / (RAND_MAX + 1.0)) * (max-min) + min «max-min + 1» используется при использовании% not * . Вы поймете, почему, когда вы сделаете min = 0, max = 1. Не могли бы петерхен или @ peter-mortensen исправить это.
davepc

17

Я хотел бы дополнить отличные ответы Angry Shoe и peterchen кратким обзором состояния дел в 2015 году:

Некоторые хорошие варианты

randutils

randutilsБиблиотека (презентация) является интересной новинкой, предлагая простой интерфейс и (объявившего) надежные случайные возможности. У него есть недостатки, заключающиеся в том, что он добавляет зависимости от вашего проекта, и, будучи новым, он не был тщательно протестирован. В любом случае, поскольку он бесплатный (лицензия MIT) и предназначен только для заголовков, я думаю, что стоит попробовать.

Минимальный образец: бросок кубика

#include <iostream>
#include "randutils.hpp"
int main() {
    randutils::mt19937_rng rng;
    std::cout << rng.uniform(1,6) << "\n";
}

Даже если библиотека не интересует, на веб-сайте ( http://www.pcg-random.org/ ) можно найти много интересных статей на тему генерации случайных чисел в целом и библиотеки C ++ в частности.

Boost.Random

Boost.Random (документация) является библиотекой , которая вдохновила C++11«S <random>, с которым разделяет большую часть интерфейса. Хотя теоретически также являясь внешней зависимостью, Boostк настоящему времени имеет статус «квазистандартной» библиотеки, и ее Randomмодуль можно рассматривать как классический выбор для генерации случайных чисел хорошего качества. Он имеет два преимущества по сравнению с C++11решением:

  • он более переносимый, ему просто нужна поддержка компилятора для C ++ 03
  • его random_deviceметоды использования системы , специфичные для предложения посевные хорошего качества

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

Минимальный образец: бросок кубика

#include <iostream>
#include <boost/random.hpp>
#include <boost/nondet_random.hpp>

int main() {
    boost::random::random_device                  rand_dev;
    boost::random::mt19937                        generator(rand_dev());
    boost::random::uniform_int_distribution<>     distr(1, 6);

    std::cout << distr(generator) << '\n';
}

Хотя минимальный образец работает хорошо, в реальных программах следует добавить пару улучшений:

  • сделать : генератор довольно пухлый (> 2 KB) и лучше не выделяется в стекеmt19937thread_local
  • seed mt19937с более чем одним целым числом: Mersenne Twister имеет большое состояние и может использовать больше энтропии во время инициализации

Некоторые не очень хорошие варианты

Библиотека C ++ 11

Будучи наиболее идиоматическим решением, <random>библиотека не предлагает многого в обмен на сложность своего интерфейса даже для базовых нужд. Недостаток заключается в std::random_deviceтом, что стандарт не требует минимального качества для своего вывода (при условии, что он entropy()возвращает 0), и с 2015 года MinGW (не самый используемый компилятор, но вряд ли эзотерический выбор) всегда будет печатать 4на минимальном образце.

Минимальный образец: бросок кубика

#include <iostream>
#include <random>
int main() {
    std::random_device                  rand_dev;
    std::mt19937                        generator(rand_dev());
    std::uniform_int_distribution<int>  distr(1, 6);

    std::cout << distr(generator) << '\n';
}

Если реализация не гнилая, это решение должно быть эквивалентно решению Boost, и применимы те же предложения.

Решение Годо

Минимальный образец: бросок кубика

#include <iostream>
#include <random>

int main() {
    std::cout << std::randint(1,6);
}

Это простое, эффективное и аккуратное решение. Единственный дефект, для компиляции потребуется время - около двух лет, при условии, что C ++ 17 будет выпущен вовремя и экспериментальная randintфункция будет утверждена в новом стандарте. Возможно, к тому времени улучшатся и гарантии качества посева.

Хуже-это-лучшее решение

Минимальный образец: бросок кубика

#include <cstdlib>
#include <ctime>
#include <iostream>

int main() {
    std::srand(std::time(nullptr));
    std::cout << (std::rand() % 6 + 1);
}

Старое решение C считается вредным, и по уважительным причинам (см. Другие ответы здесь или этот подробный анализ ). Тем не менее, у него есть свои преимущества: он простой, портативный, быстрый и честный, в том смысле, что известно, что получаемые случайные числа вряд ли являются приличными, и поэтому у человека нет соблазна использовать их для серьезных целей.

Решение бухгалтерского тролля

Минимальный образец: бросок кубика

#include <iostream>

int main() {
    std::cout << 9;   // http://dilbert.com/strip/2001-10-25
}

Хотя 9 - несколько необычный результат для обычного броска кубика, нужно восхищаться превосходным сочетанием хороших качеств в этом решении, которое оказалось самым быстрым, простым, самым удобным для кеширования и самым портативным. Заменяя 9 на 4, можно получить идеальный генератор для любого типа подземелий и гибели драконов, при этом избегая при этом обозначенных символами значений 1, 2 и 3. Единственный небольшой недостаток заключается в том, что из-за дурного настроения бухгалтерских троллей Дилберта, эта программа фактически порождает неопределенное поведение.


randutilsБиблиотека называется PCG Теперь.
tay10r

10

Если RAND_MAX32767, вы можете легко удвоить количество бит.

int BigRand()
{
    assert(INT_MAX/(RAND_MAX+1) > RAND_MAX);
    return rand() * (RAND_MAX+1) + rand();
}

Я не думаю, что это работает. Генераторы псевдослучайных чисел обычно детерминированы. Например, если первый randвызов возвращается, 0x1234а второй 0x5678- вы получаете 0x12345678. Это единственный номер, который начинается с которого вы можете получить 0x1234, потому что следующий номер всегда будет 0x5678. Вы получаете 32-битные результаты, но у вас есть только 32768 возможных чисел.
user694733

@ user694733 хороший генератор случайных чисел имеет период, превышающий количество выходных данных, которые он может сгенерировать, поэтому 0x1234 не всегда будет сопровождаться 0x5678.
Марк Рэнсом

9

Если можете, используйте Boost . Мне повезло с их случайной библиотекой .

uniform_int должен делать то, что хочешь.


Я проделал некоторую работу над uniform_int с помощью merseinne twister, и, к сожалению, для определенных диапазонов значения, возвращаемые uniform_int, не так однородны, как я ожидал. Например, uniform_int <> (0, 3) имеет тенденцию производить больше
нулей, чем единиц

@ScaryAardvark, похоже, плохая реализация того uniform_intвремени. Получить непредвзятый вывод довольно легко, здесь было несколько вопросов, демонстрирующих метод.
Марк Рэнсом

@Mark Ransom. Да, полностью согласен.
ScaryAardvark

8

Если вас беспокоит случайность, а не скорость, вам следует использовать безопасный метод генерации случайных чисел. Есть несколько способов сделать это ... Самый простой - использовать генератор случайных чисел OpenSSL .

Вы также можете написать свой собственный, используя алгоритм шифрования (например, AES ). Выбирая начальное число и IV, а затем непрерывно повторно шифруя выходные данные функции шифрования. Использовать OpenSSL проще, но менее мужественно.


Я не могу использовать сторонние библиотеки? Я ограничен только C ++.
anand

Затем идите по мужскому пути, реализуйте AES или какой-нибудь другой алгоритм шифрования.
SoapBox

2
RC4 тривиален для кодирования и достаточно случайен для всех практических целей (кроме WEP, но это не полностью ошибка RC4). Я серьезно, это невероятно тривиальный код. Примерно 20 строк или около того. Запись в Википедии имеет псевдокод.
Стив Джессоп,

4
Почему нельзя использовать сторонний код? Если это вопрос домашнего задания, вы должны так сказать, потому что многие люди скорее дадут полезные советы, чем предоставят полные решения в этом случае. Если это не домашнее задание, пни парня, который говорит «без стороннего кода», потому что он идиот.
DevSolar 02

Более прямая ссылка на документы функции OpenSSL rand (): openssl.org/docs/crypto/rand.html#
DevSolar

5

Вы должны искать RAND_MAXсвой конкретный компилятор / среду. Я думаю, вы увидите эти результаты, если rand()произведете случайное 16-битное число. (похоже, вы предполагаете, что это будет 32-битное число).

Я не могу обещать, что это ответ, но, пожалуйста, опубликуйте свою ценность RAND_MAXи немного более подробно о вашей среде.


3

Проверить что RAND_MAX находится в вашей системе - я предполагаю, что это всего лишь 16 бит, и ваш диапазон слишком велик для этого.

Кроме того, см. Это обсуждение: Генерация случайных целых чисел в желаемом диапазоне и примечания по использованию (или нет) функции C rand () .


Хорошо, RAND_MAX - 32767. Я использую платформу Windows C ++. Есть ли другой метод для генерации случайных чисел с равномерным распределением?
anand

2

Это не код, но эта логика может вам помочь.

static double rnd(void)
{
   return (1.0 / (RAND_MAX + 1.0) * ((double)(rand())) );
}

static void InitBetterRnd(unsigned int seed)
{
    register int i;
    srand( seed );
    for( i = 0; i < POOLSIZE; i++){
        pool[i] = rnd();
    }
}

 // This function returns a number between 0 and 1
 static double rnd0_1(void)
 {
    static int i = POOLSIZE-1;
    double r;

    i = (int)(POOLSIZE*pool[i]);
    r = pool[i];
    pool[i] = rnd();
    return (r);
}

2

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

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


1

Это должно обеспечить равномерное распределение по диапазону [low, high)без использования числа с плавающей запятой, если общий диапазон меньше RAND_MAX.

uint32_t rand_range_low(uint32_t low, uint32_t high)
{
    uint32_t val;
    // only for 0 < range <= RAND_MAX
    assert(low < high);
    assert(high - low <= RAND_MAX);

    uint32_t range = high-low;
    uint32_t scale = RAND_MAX/range;
    do {
        val = rand();
    } while (val >= scale * range); // since scale is truncated, pick a new val until it's lower than scale*range
    return val/scale + low;
}

а для значений больше RAND_MAX вам нужно что-то вроде

uint32_t rand_range(uint32_t low, uint32_t high)
{
    assert(high>low);
    uint32_t val;
    uint32_t range = high-low;
    if (range < RAND_MAX)
        return rand_range_low(low, high);
    uint32_t scale = range/RAND_MAX;
    do {
        val = rand() + rand_range(0, scale) * RAND_MAX; // scale the initial range in RAND_MAX steps, then add an offset to get a uniform interval
    } while (val >= range);
    return val + low;
}

Примерно так работает std :: uniform_int_distribution.


0

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

Но имейте в виду, что случайность не обязательно одинакова.

Изменить: я добавил «небольшой образец», чтобы уточнить.


«равномерно распределенный» имеет четко определенное значение, и стандартные генераторы случайных чисел обычно приближаются к нему.
peterchen

Да, вы правы, генераторы случайных чисел должны выдавать результат, который с течением времени обычно имеет одинаковое распределение. Я предполагаю, что моя точка зрения заключается в том, что в небольшом количестве экземпляров (6, как показано в примере) результат не всегда будет однородным.
Kluge

Клюге прав. Равномерное распределение в небольшой выборке указывает на то, что выборка определенно не случайна.
Bill the Lizard

1
Билл, это не означает ничего подобного. Маленькие выборки в большинстве своем бессмысленны, но если предполагается, что ГСЧ должен быть однородным, а результат - однородным, почему это хуже, чем неоднородная маленькая выборка?
Дэн Дайер

2
Значительные распределения в любом случае указывают на неслучайность: я думаю, что Билл просто означает, что шесть равноотстоящих результатов также будут подозрительными. В OP 6 значений лежат в диапазоне 32k / 4M, или <1% от желаемого диапазона. Вероятность того, что это ложное срабатывание, слишком мала, чтобы с ней спорить.
Стив Джессоп,

0

Решение, данное man 3 rand для числа от 1 до 10 включительно:

j = 1 + (int) (10.0 * (rand() / (RAND_MAX + 1.0)));

В вашем случае это будет:

j = min + (int) ((max-min+1) * (rand() / (RAND_MAX + 1.0)));

Конечно, это не идеальная случайность или однородность, как указывают некоторые другие сообщения, но этого достаточно для большинства случаев.


1
Это просто меняет распределение, чтобы оно выглядело более равномерным, но на самом деле этого больше нет даже для больших диапазонов (например, в случае с OP)
Mooing Duck

0

@Решение ((double) rand() / (RAND_MAX+1)) * (max-min+1) + min

Предупреждение : не забывайте, что из-за растяжения и возможных ошибок точности (даже если RAND_MAX было достаточно большим), вы сможете генерировать только равномерно распределенные «ячейки», а не все числа в [min, max].


@ Решение: Бигранд

Предупреждение : обратите внимание, что это удваивает биты, но по-прежнему не сможет сгенерировать все числа в вашем диапазоне в целом, т.е. не обязательно верно, что BigRand () будет генерировать все числа между в своем диапазоне.


Информация : ваш подход (по модулю) "хорош", пока диапазон rand () превышает ваш диапазон интервалов, а rand () является "равномерным". Ошибка не более чем для первых максимальных - минимальных чисел составляет 1 / (RAND_MAX +1).

Также я предлагаю переключиться на новый случайный пакет e и в C ++ 11, который предлагает лучшие и более разнообразные реализации, чем rand ().


0

Это решение, которое я придумал:

#include "<stdlib.h>"

int32_t RandomRange(int32_t min, int32_t max) {
    return (rand() * (max - min + 1) / (RAND_MAX + 1)) + min;
}

Это бакет-решение, концептуально похожее на решения, которые используются rand() / RAND_MAXдля получения диапазона с плавающей запятой от 0 до 1, а затем округления его в бакет. Однако он использует чисто целочисленную математику и использует преимущества целочисленного деления для округления значения до ближайшего сегмента.

Делается несколько предположений. Во-первых, предполагается, что RAND_MAX * (max - min + 1)он всегда умещается в int32_t. Если RAND_MAXиспользуется 32767 и используются 32-битные вычисления int, максимальный диапазон, который вы можете иметь, равен 32767. Если ваша реализация имеет гораздо больший RAND_MAX, вы можете преодолеть это, используя int64_tдля вычисления большее целое число (например, ). Во-вторых, если int64_tиспользуется, но RAND_MAXпо-прежнему 32767, в диапазонах больше, чем RAND_MAXвы начнете получать «дыры» в возможных выходных числах. Это, вероятно, самая большая проблема любого решения, связанного с масштабированием rand().

Тем не менее, тестирование на большом количестве итераций показывает, что этот метод очень единообразен для небольших диапазонов. Однако возможно (и вероятно), что математически это имеет небольшую погрешность и, возможно, вызывает проблемы при приближении диапазона RAND_MAX. Проверьте это на себе и решите, соответствует ли он вашим потребностям.


-1

Конечно, следующий код выдаст вам не случайные числа, а псевдослучайное число. Используйте следующий код

#define QUICK_RAND(m,n) m + ( std::rand() % ( (n) - (m) + 1 ) )

Например:

int myRand = QUICK_RAND(10, 20);

Вы должны позвонить

srand(time(0));  // Initialize random number generator.

иначе числа не будут случайными.


1
Вопрос заключается в равномерном распределении. Предлагаемое решение не приведет к равномерному распределению. Стандартная библиотека C ++ имеет средства для генерации псевдослучайных чисел . Те делают обеспечивают равномерное распределение, если требуется.
Inspectable 01

-3

Я только что нашел это в Интернете. Это должно работать:

DWORD random = ((min) + rand()/(RAND_MAX + 1.0) * ((max) - (min) + 1));

Пожалуйста, поясните, для чего они вам нужны, существует множество алгоритмов для ГПСЧ. Кроме того, будет проще, если вы отредактируете свой основной вопрос вместо того, чтобы публиковать ответы.
peterchen

Это работает лучше всего для меня ... Я могу лучше распределить случайные числа с помощью этой формулы ...
anand

4
Если ваш диапазон превышает RAND_MAX, результаты могут быть не равномерным. То есть в диапазоне есть значения, которые не будут представлены независимо от того, сколько раз вы вызываете вашу функцию.
dmckee --- котенок экс-модератора

4
Кроме того, если max и min являются беззнаковыми int, а min равно 0, а max равно MAX_UINT, то ((max) - (min) +1) будет 0, и результат всегда будет 0. Остерегайтесь переполнения при выполнении этого вида математики! Как отмечает dmckee, это расширяет распределение по диапазону назначения, но не гарантирует уникальных значений более RAND_MAX.
jesup
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.