Как сгенерировать случайное целое число из диапазона

108

Это продолжение ранее опубликованного вопроса:

Как сгенерировать случайное число на C?

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

Как бы я это сделал?

Джейми Килинг
источник
3
Если вы посмотрите на второй ответ на вопрос, на который вы ссылаетесь, вы получите ответ. rand ()% 6.
Mats Fredriksson
2
Я не понимал, как это работает, поэтому решил для ясности задать отдельный вопрос.
Джейми Килинг,
2
Случайная мысль: если вы опросите случайное количество программистов, вы обнаружите, что случайное количество из них случайным образом думают о способах случайного генерирования чисел. Учитывая, что Вселенная управляется точными и предсказуемыми законами, разве не интересно, что мы пытаемся генерировать вещи более случайным образом? Подобные вопросы всегда приводят к появлению более 10 тысяч плакатов.
Armstrongest
2
@Mats rand ()% 6 может вернуть 0. Не годится для игры в кости.
new123456 05
Можете ли вы пометить stackoverflow.com/a/6852396/419 как принятый ответ вместо ответа, который на него ссылается :) Спасибо.
Kev

Ответы:

173

Все ответы пока математически неверны. Возврат 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это достаточно высокая точность, чтобы заставить его работать; возможно нет. Я не знаю и не хочу разбираться в этом; в любом случае ответ зависит от системы.

Правильный способ - использовать целочисленную арифметику. То есть вам нужно что-то вроде следующего:

#include <stdlib.h> // For random(), RAND_MAX

// Assumes 0 <= max <= RAND_MAX
// Returns in the closed interval [0, max]
long random_at_most(long max) {
  unsigned long
    // max <= RAND_MAX < ULONG_MAX, so this is okay.
    num_bins = (unsigned long) max + 1,
    num_rand = (unsigned long) RAND_MAX + 1,
    bin_size = num_rand / num_bins,
    defect   = num_rand % num_bins;

  long x;
  do {
   x = random();
  }
  // This is carefully written not to overflow
  while (num_rand - defect <= (unsigned long)x);

  // Truncated division is intentional
  return x/bin_size;
}

Петля необходима для получения идеально равномерного распределения. Например, если вам заданы случайные числа от 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), включая отрицательные значения.

Райан Райх
источник
1
@ Адам Розенфилд, @ Райан Райх: В связанном вопросе, на который ответил Адам: stackoverflow.com/questions/137783/… наиболее популярный ответ: тогда использование «модуля» было бы неверным, нет? Чтобы сгенерировать 1..7 из 1..21, следует использовать процедуру, описанную Райаном. Пожалуйста, поправьте меня, если я ошибаюсь.
Arvind
1
При дальнейшем рассмотрении другая проблема заключается в том, что это не сработает, когда max - min > RAND_MAX, что более серьезно, чем проблема, о которой я говорил выше (например, VC ++ имеет RAND_MAXтолько 32767).
Interjay
2
Цикл while можно было бы сделать более читабельным. Вместо того, чтобы выполнять присваивание в условном выражении, вы, вероятно, захотите do {} while().
theJPster
4
Привет, этот ответ цитируется в книге Comet OS;) Впервые я вижу это в учебной книге
vpuente
3
Это также цитируется в книге OSTEP :) pages.cs.wisc.edu/~remzi/OSTEP (Глава 9, страница 4)
rafascar
33

Следуя ответу @Ryan Reich, я подумал, что предлагаю свою очищенную версию. Первая проверка границ не требуется, учитывая вторую проверку границ, и я сделал ее итеративной, а не рекурсивной. Он возвращает значения в диапазоне [min, max], где max >= minи 1+max-min < RAND_MAX.

unsigned int rand_interval(unsigned int min, unsigned int max)
{
    int r;
    const unsigned int range = 1 + max - min;
    const unsigned int buckets = RAND_MAX / range;
    const unsigned int limit = buckets * range;

    /* Create equal size buckets all in a row, then fire randomly towards
     * the buckets until you land in one of them. All buckets are equally
     * likely. If you land off the end of the line of buckets, try again. */
    do
    {
        r = rand();
    } while (r >= limit);

    return min + (r / buckets);
}
theJPster
источник
28
Обратите внимание, что это застрянет в бесконечном цикле, если диапазон> = RAND_MAX. Спросите меня, откуда я знаю: /
theJPster
24
Откуда вы знаете!?
Fantastic Mr Fox
1
Обратите внимание, что вы сравниваете int с беззнаковым int (r> = limit). Проблема легко решается созданием limitint (и, возможно, bucketтоже), поскольку RAND_MAX / range< INT_MAXи buckets * range<= RAND_MAX. РЕДАКТИРОВАТЬ: я отправил и отредактировал предложение.
rrrrrrrrrrrrrrrr 03
решение от @Ryan Reich по-прежнему дает мне лучшее (менее предвзятое) распределение
Владимир
20

Вот формула, если вам известны максимальное и минимальное значения диапазона и вы хотите сгенерировать числа, включительно между диапазоном:

r = (rand() % (max + 1 - min)) + min
Саттар
источник
9
Как отмечено в ответе Райана, это дает необъективный результат.
Дэвид Волевер
6
Предвзятый результат, потенциальное intпереполнение max+1-min.
chux
1
это работает только с целыми числами min и max. Если минимальное и максимальное значения являются плавающими, операцию% выполнить невозможно
Тайоли Франческо
17
unsigned int
randr(unsigned int min, unsigned int max)
{
       double scaled = (double)rand()/RAND_MAX;

       return (max - min +1)*scaled + min;
}

Смотрите здесь другие варианты.

нет
источник
2
@ S.Lott - не совсем. Каждый распределяет случаи с чуть более высокими шансами по-разному, вот и все. Двойная математика создает впечатление, что там больше точности, но вы могли бы так же легко использовать (((max-min+1)*rand())/RAND_MAX)+minи, вероятно, получить точно такое же распределение (при условии, что RAND_MAX достаточно мал относительно int, чтобы не переполняться).
Steve314,
4
Это немного опасно: это может (очень редко) вернуться max + 1, если оно есть rand() == RAND_MAX, или rand()очень близко к нему, RAND_MAXа ошибки с плавающей запятой вытесняют окончательный результат max + 1. Чтобы быть в безопасности, вы должны убедиться, что результат находится в пределах допустимого диапазона, прежде чем возвращать его.
Марк Дикинсон,
1
@Christoph: Я согласен RAND_MAX + 1.0. Я все еще не уверен, что этого достаточно, чтобы предотвратить max + 1возврат: в частности, + minв конце есть раунд, который может привести max + 1к большим значениям rand (). Безопаснее вообще отказаться от этого подхода и использовать целочисленную арифметику.
Марк Дикинсон,
3
Если 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.
Марк Дикинсон,
1
Ошибка для randr(0, UINT_MAX): всегда генерирует 0.
chux - Reinstate Monica
12

Не могли бы вы просто сделать:

srand(time(NULL));
int r = ( rand() % 6 ) + 1;

%- оператор модуля. По сути, он просто разделится на 6 и вернет остаток ... от 0 до 5.

Armstrongest
источник
1
Результат будет от 1 до 6. Вот для чего нужен + 1.
Armstrongest
4
Саймон, покажи мне libc, которая используется где угодно, где rand()включает младшие биты состояния генератора (если он использует LCG). Я пока не видел ни одного - все они (да, включая MSVC с RAND_MAX, равным 32767) удаляют младшие биты. Использование модуля не рекомендуется по другим причинам, а именно из-за того, что он искажает распределение в пользу меньших чисел.
Joey
@Johannes: Значит, можно с уверенностью сказать, что игровые автоматы не используют модуль?
Armstrongest
Как мне исключить 0? Кажется, что если я запустил его в цикле из 30, возможно, во второй или третий раз, когда он запустится, примерно на полпути будет 0. Это какая-то случайность?
Джейми Килинг,
@Johannes: Возможно, в настоящее время это не такая большая проблема, но традиционно использование младших битов не рекомендуется. c-faq.com/lib/randrange.html
jamesdlin
9

Для тех, кто понимает проблему смещения, но не переносит непредсказуемое время работы методов, основанных на отклонении, эта серия дает постепенно менее смещенное случайное целое число в [0, n-1]интервале:

r = n / 2;
r = (rand() * n + r) / (RAND_MAX + 1);
r = (rand() * n + r) / (RAND_MAX + 1);
r = (rand() * n + r) / (RAND_MAX + 1);
...

Это достигается путем синтеза высокоточного случайного числа i * log_2(RAND_MAX + 1)битов с фиксированной точкой (где i- количество итераций) и выполнения длинного умножения на n.

Когда количество битов достаточно велико по сравнению с n, смещение становится неизмеримо малым.

Не имеет значения, RAND_MAX + 1меньше ли оно n(как в этом вопросе ) или не является степенью двойки, но следует проявлять осторожность, чтобы избежать целочисленного переполнения, если RAND_MAX * nоно велико.

sh1
источник
2
RAND_MAXчасто INT_MAX, поэтому RAND_MAX + 1-> UB (как INT_MIN)
chux - Reinstate Monica
@chux это то, что я имею в виду, говоря, что "следует проявлять осторожность, чтобы избежать переполнения целых чисел, если RAND_MAX * nоно велико". Вам необходимо организовать использование соответствующих типов в соответствии с вашими требованиями.
sh1
@chux " RAND_MAXчасто INT_MAX" да, но только в 16-битных системах! Любая разумно современная архитектура поставит INT_MAX2 ^ 32/2 и RAND_MAX2 ^ 16/2. Это неверное предположение?
кот
2
@cat Протестировал сегодня 2 32-битных intкомпилятора, нашел RAND_MAX == 32767на одном и RAND_MAX == 2147483647на другом. Мой общий опыт (десятилетия) RAND_MAX == INT_MAXтаков чаще. Так не согласен , что достаточно современный 32-битная архитектура, безусловно , есть RAND_MAXв 2^16 / 2. Поскольку спецификация C позволяет 32767 <= RAND_MAX <= INT_MAX, я все равно кодирую это, а не тенденцию.
chux - Восстановить Монику
3
По-прежнему охвачено «необходимо соблюдать осторожность, чтобы избежать переполнения целых чисел».
sh1
4

Чтобы избежать смещения по модулю (предложенного в других ответах), вы всегда можете использовать:

arc4random_uniform(MAX-MIN)+MIN

Где «MAX» - это верхняя граница, а «MIN» - нижняя граница. Например, для чисел от 10 до 20:

arc4random_uniform(20-10)+10

arc4random_uniform(10)+10

Простое решение и лучше, чем использование "rand ()% N".

магамиг
источник
1
Уууу, это в миллиард раз лучше, чем другие ответы. Стоит отметить, что вам нужно #include <bsd/stdlib.h>сначала. Кроме того, есть идеи, как получить это в Windows без MinGW или CygWin?
кот
1
Нет, сам по себе он не лучше других ответов, потому что другие ответы более общие. Здесь вы ограничены arc4random, другие ответы позволяют вам выбирать другой случайный источник, работать с разными типами чисел ... и, наконец, что не менее важно, они могут помочь кому-то понять проблему. Не забывайте, что этот вопрос также интересен другим людям, у которых могут быть особые требования или нет доступа к arc4random ... Тем не менее, если у вас есть доступ к нему и вы хотите получить быстрое решение, это действительно очень хороший ответ 😊
K. Biermann
4

Вот несколько более простой алгоритм, чем решение Райана Райха:

/// Begin and end are *inclusive*; => [begin, end]
uint32_t getRandInterval(uint32_t begin, uint32_t end) {
    uint32_t range = (end - begin) + 1;
    uint32_t limit = ((uint64_t)RAND_MAX + 1) - (((uint64_t)RAND_MAX + 1) % range);

    /* Imagine range-sized buckets all in a row, then fire randomly towards
     * the buckets until you land in one of them. All buckets are equally
     * likely. If you land off the end of the line of buckets, try again. */
    uint32_t randVal = rand();
    while (randVal >= limit) randVal = rand();

    /// Return the position you hit in the bucket + begin as random number
    return (randVal % range) + begin;
}

Example (RAND_MAX := 16, begin := 2, end := 7)
    => range := 6  (1 + end - begin)
    => limit := 12 (RAND_MAX + 1) - ((RAND_MAX + 1) % range)

The limit is always a multiple of the range,
so we can split it into range-sized buckets:
    Possible-rand-output: 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16
    Buckets:             [0, 1, 2, 3, 4, 5][0, 1, 2, 3, 4, 5][X, X, X, X, X]
    Buckets + begin:     [2, 3, 4, 5, 6, 7][2, 3, 4, 5, 6, 7][X, X, X, X, X]

1st call to rand() => 13
     13 is not in the bucket-range anymore (>= limit), while-condition is true
         retry...
2nd call to rand() => 7
     7 is in the bucket-range (< limit), while-condition is false
         Get the corresponding bucket-value 1 (randVal % range) and add begin
    => 3
К. Бирманн
источник
1
RAND_MAX + 1может легко переполнить intдобавление. В этом случае (RAND_MAX + 1) % rangeбудут получены сомнительные результаты. Подумайте(RAND_MAX + (uint32_t)1)
chux
2

Хотя Райан прав, решение может быть намного проще в зависимости от того, что известно об источнике случайности. Чтобы переформулировать проблему:

  • Существует источник случайности, выводящий целые числа в диапазоне [0, MAX)с равномерным распределением.
  • Цель состоит в том, чтобы произвести равномерно распределенные случайные целые числа в диапазоне [rmin, rmax]где 0 <= rmin < rmax < MAX.

По моему опыту, если количество бункеров (или «ящиков») значительно меньше диапазона исходных чисел, а исходный источник криптографически надежен - нет необходимости повторять всю эту ригамаролу, и простое деление по модулю будет достаточно (например output = rnd.next() % (rmax+1), если rmin == 0) и производят случайные числа, которые распределяются равномерно "достаточно" и без какой-либо потери скорости. Ключевым фактором является источник случайности (например, дети, не пытайтесь делать это дома rand()).

Вот пример / доказательство того, как это работает на практике. Я хотел генерировать случайные числа от 1 до 22, имея криптографически надежный источник, который генерирует случайные байты (на основе Intel RDRAND). Результат:

Rnd distribution test (22 boxes, numbers of entries in each box):     
 1: 409443    4.55%
 2: 408736    4.54%
 3: 408557    4.54%
 4: 409125    4.55%
 5: 408812    4.54%
 6: 409418    4.55%
 7: 408365    4.54%
 8: 407992    4.53%
 9: 409262    4.55%
10: 408112    4.53%
11: 409995    4.56%
12: 409810    4.55%
13: 409638    4.55%
14: 408905    4.54%
15: 408484    4.54%
16: 408211    4.54%
17: 409773    4.55%
18: 409597    4.55%
19: 409727    4.55%
20: 409062    4.55%
21: 409634    4.55%
22: 409342    4.55%   
total: 100.00%

Это настолько близко к единообразию, насколько мне нужно для моей цели (справедливый бросок костей, создание криптографически стойких кодовых книг для шифровальных машин Второй мировой войны, таких как http://users.telenet.be/d.rijmenants/en/kl-7sim.htm и т. Д. ). Вывод не показывает заметной предвзятости.

Вот источник криптографически стойкого (истинного) генератора случайных чисел: Intel Digital Random Number Generator и образец кода, который производит 64-битные (беззнаковые) случайные числа.

int rdrand64_step(unsigned long long int *therand)
{
  unsigned long long int foo;
  int cf_error_status;

  asm("rdrand %%rax; \
        mov $1,%%edx; \
        cmovae %%rax,%%rdx; \
        mov %%edx,%1; \
        mov %%rax, %0;":"=r"(foo),"=r"(cf_error_status)::"%rax","%rdx");
        *therand = foo;
  return cf_error_status;
}

Я скомпилировал его на 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). Любые идеи?
кот
Во-первых, я не знаю, действительно ли ваш процессор поддерживает инструкцию RDRAND. Ваша ОС довольно новая, но ЦП может и не быть. Во-вторых (но это менее вероятно) - я понятия не имею, какой ассемблер включает в себя Ubuntu (а Ubuntu имеет тенденцию быть довольно обратной по отношению к пакетам обновления). Посетите сайт Intel, на который я ссылался, чтобы узнать, как проверить, поддерживает ли ваш процессор RDRAND.
Mouse
У вас действительно есть хорошие аргументы. Что я до сих пор не могу понять, так это того, что не так rand(). Я попробовал несколько тестов и опубликовал этот вопрос, но пока не могу найти окончательного ответа.
myradio
1

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

static uint32_t randomInRange(uint32_t a,uint32_t b) {
    uint32_t v;
    uint32_t range;
    uint32_t upper;
    uint32_t lower;
    uint32_t mask;

    if(a == b) {
        return a;
    }

    if(a > b) {
        upper = a;
        lower = b;
    } else {
        upper = b;
        lower = a; 
    }

    range = upper - lower;

    mask = 0;
    //XXX calculate range with log and mask? nah, too lazy :).
    while(1) {
        if(mask >= range) {
            break;
        }
        mask = (mask << 1) | 1;
    }


    while(1) {
        v = rand() & mask;
        if(v <= range) {
            return lower + v;
        }
    }

}

Следующий простой код позволяет вам посмотреть на распределение:

int main() {

    unsigned long long int i;


    unsigned int n = 10;
    unsigned int numbers[n];


    for (i = 0; i < n; i++) {
        numbers[i] = 0;
    }

    for (i = 0 ; i < 10000000 ; i++){
        uint32_t rand = random_in_range(0,n - 1);
        if(rand >= n){
            printf("bug: rand out of range %u\n",(unsigned int)rand);
            return 1;
        }
        numbers[rand] += 1;
    }

    for(i = 0; i < n; i++) {
        printf("%u: %u\n",i,numbers[i]);
    }

}
Эндрю Чемберс
источник
Становится довольно неэффективным, когда вы отклоняете числа из rand (). Это будет особенно неэффективно, когда диапазон имеет размер, который можно записать как 2 ^ k + 1. Тогда почти половина всех ваших попыток из медленного вызова rand () будет отклонена условием. Может быть, лучше вычислить RAND_MAX по модулю диапазона. Типа: v = rand(); if (v > RAND_MAX - (RAND_MAX % range) -> reject and try again; else return v % range;я понимаю, что по модулю намного медленнее, чем маскирование, но я все же думаю ... что это нужно проверить.
Øystein Schønning-Johansen
rand()возвращает intв диапазоне [0..RAND_MAX]. Этот диапазон легко может быть поддиапазоном, uint32_tи тогда randomInRange(0, ,b)никогда не будут генерироваться значения в этом диапазоне (INT_MAX...b].
chux
0

Вернет число с плавающей запятой в диапазоне [0,1]:

#define rand01() (((double)random())/((double)(RAND_MAX)))
Геремия
источник