Як генерувати випадкове ціле число в межах діапазону


108

Це далі з раніше опублікованого питання:

Як генерувати випадкове число в С?

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

Як би я пішов робити це?


3
якщо ви подивитеся на другу відповідь на питання, яке ви посилаєтесь, у вас є відповідь. rand ()% 6.
Матс Фредрікссон

2
Я не розумів, як це працює, тому вирішив поставити окреме питання для наочності.
Джеймі Кілінг

2
Випадкова думка: Якщо ви опитували випадковий переріз програмістів, ви виявите, що випадкове їх число випадковим чином мислить способи випадкового генерування чисел. Зважаючи на те, що Всесвіт управляється чіткими і передбачуваними законами, чи не цікаво, що ми намагаємося генерувати речі більш випадковим чином? Питання, подібні цьому, завжди мають тенденцію виводити плакати 10k +.
Армстронгест

2
@Mats rand ()% 6 може повернути 0. Не добре.
new123456

Чи можете ви позначити stackoverflow.com/a/6852396/419 як прийняту відповідь замість відповіді, що посилається на неї :) Дякуємо
Кев

Відповіді:


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()тому, що він має кращий розподіл (як це відмічено на сторінці man 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
@Adam Розенфельд, @ Райан Рейх: У зв'язку з цим питанням , де Адам відповів: stackoverflow.com/questions/137783 / ... самого upvoted відповіді: Використання «модуля» б тоді неправильно, немає? Щоб генерувати 1..7 з 1..21, слід використовувати процедуру, описану Райаном. Виправте мене, якщо я помиляюся.
Арвінд

1
Подальший огляд, інше питання полягає в тому, що це не спрацює, коли max - min > RAND_MAX, що є більш серйозним, ніж проблема, про яку я говорив вище (наприклад, VC ++ має RAND_MAXлише 32767).
interjay

2
Цикл у той час можна зробити більш читабельним. Замість того, щоб виконувати завдання в умовному, ви, мабуть, хочете do {} while().
14:45

4
Гей, цю відповідь наводить книга Comet OS;) Перший раз я бачу, що в навчальній книзі
vpuente

3
Це також цитується у книзі OSTEP :) pages.cs.wisc.edu/~remzi/OSTEP (глава 9, стор. 4)
rafascar

33

Виходячи з відповіді @Ryan Reich, я подумав, що запропоную свою очищену версію. Перевірка першого межі не потрібна, враховуючи перевірку другої межі, і я зробив її більш ітеративною, а не рекурсивною. Він повертає значення в діапазоні [хв, макс], де 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);
}

28
Зверніть увагу, що він застрягне в нескінченному циклі, якщо діапазон> = RAND_MAX. Запитайте мене, як я знаю: /
theJPster

24
Звідки ти знаєш!?
Фантастичний містер Фокс

1
Зауважте, що ви порівнюєте int з неподписаним int (r> = межа). Проблема легко вирішується шляхом limitвведення int (і, можливо, bucketтеж), оскільки RAND_MAX / range< INT_MAXі buckets * range<= RAND_MAX. EDIT: Я надсилаю та редагую пропозицію.
rrrrrrrrrrrrrrrr

рішення від @ Ryan Reich все ще дає мені кращий (менш упереджений) розподіл
Володимир

20

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

r = (rand() % (max + 1 - min)) + min

9
Як зазначається у відповіді Райана, це призводить до упередженого результату.
Девід Уолвер

6
Об'єктивний результат, потенційний intперелив max+1-min.
chux

1
ця робота лише з цілими min та max. Якщо min і max поплавці, операцію% зробити неможливо
Taioli Francesco

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 - Відновлення Моніки

12

Чи не просто ви зробите:

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

%є оператором модуля. По суті, він просто розділиться на 6, а решту поверне ... від 0 до 5


1
Це дасть результати 1 - 6. Саме для цього і є + 1.
Армстронгест

4
Саймоне, покажи мені libc у будь-якому місці, де rand()включаються біти низького порядку стану генератора (якщо він використовує LCG). Я ще не бачив цього - усі вони (так, включаючи MSVC з RAND_MAX лише 32767) видаляють біти низького порядку. Використання модуля не рекомендується з інших причин, а саме, щоб воно перекривало розподіл на користь менших чисел.
Joey

@Johannes: Тож можна сказати, що ігрові автомати не використовують модуль?
Армстронджест

Як я можу виключити 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він великий.


2
RAND_MAXчасто INT_MAX, так RAND_MAX + 1-> UB (як INT_MIN)
chux - Відновіть Моніку

@chux - це те, що я маю на увазі "треба бути обережним, щоб уникнути переливу цілих чисел, якщо RAND_MAX * nвеликий". Вам потрібно домовитись про використання відповідних типів для ваших вимог.
sh1

@chux " RAND_MAXчасто INT_MAX" Так, але лише в 16-бітових системах! Будь-яка досить сучасна архітектура поставить INT_MAXу 2 ^ 32/2 та RAND_MAXв 2 ^ 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
Woohoo, це в мільярд разів краще, ніж інші відповіді. Варто зазначити, що #include <bsd/stdlib.h>спочатку потрібно . Крім того, будь-яка ідея, як отримати це в Windows без MinGW або CygWin?
кіт

1
Ні, вона сама по собі не краща за інші відповіді, тому що інші відповіді є більш загальними. Тут ви обмежені лише arc4random, інші відповіді дозволяють вибрати інше випадкове джерело, оперувати різними типами чисел, ... і останнє, але не в останню чергу вони можуть допомогти комусь зрозуміти проблему. Не забувайте, що питання цікаве й для інших людей, які можуть мати якісь особливі вимоги або не мають доступу до arc4random ... Тим не менш, якщо у вас є доступ до нього і хочете швидкого вирішення, це справді дуже хороша відповідь 😊
К. Бірманн

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 та зразок коду, який виробляє 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.
Миша

У вас справді хороші моменти. Що я досі не можу отримати, це те, що так не так 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)))
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.