Як генерувати випадковий int в C?


548

Чи існує функція для генерації випадкового int числа в C? Або мені доведеться використовувати сторонні бібліотеки?


1
Довга тема в comp.lang.c про rand () та PRNG "якість" .
luser droog

Відповіді:


662

Примітка . Не використовуйте rand()для безпеки. Якщо вам потрібен криптографічно захищений номер, дивіться замість цього відповідь .

#include <time.h>
#include <stdlib.h>

srand(time(NULL));   // Initialization, should only be called once.
int r = rand();      // Returns a pseudo-random integer between 0 and RAND_MAX.

Редагування : В Linux ви можете скористатися випадковим і srandom .


194
+1 для простоти, але, напевно, добре підкреслити, що srand () слід викликати лише один раз . Також у потоковому додатку ви можете переконатися, що стан генератора зберігається на кожну нитку, і посіяйте генератор один раз для кожної нитки.
RBerteig

41
@trusktr, його складне. Ось причина: time()змінюється лише раз на секунду. Якщо ви будете отримувати набір time()для кожного дзвінка rand(), ви отримаєте однакове значення для кожного дзвінка протягом однієї секунди. Але більша причина полягає в тому, що властивості rand()та такі функції, як вони, відомі найкращим чином у випадку використання, коли вони висіваються рівно один раз на пробіг, а не на кожен виклик. Залежно від "випадковості" з неперевіреними або недоведеними властивостями призводить до неприємностей.
RBerteig

9
@trusktr для простого лінійного вродженого генератора (який rand()зазвичай є) посів насіння rand()в кращому випадку не матиме жодного ефекту, а в гіршому випадку порушує відомі якості генератора. Це глибока тема. Почніть з читання Knuth Vol 2 Глава 3 про випадкові числа як найкращий вступ до математики та підводних каменів.
RBerteig

21
Уникайте попередження компілятора з участю:srand((unsigned int)time(NULL));
GiovaMaster

9
Майте на увазі, що це все ще слабкий спосіб бачити PRNG. Лише минулого року вірус типу криптовалют в Linux зробив помилку посіву з часом, і це різко скоротило пошуковий простір. Все, що вам потрібно було зробити, це отримати гідне уявлення про те, коли відбулася інфекція, а потім спробувати насіння приблизно з того часу. Останнє, що я чув, найкращим джерелом випадковості є / dev / urandom, який, начебто, посіяний з масиву хаотичних джерел, таких як температури на обладнання. Якщо все, що ви дійсно хочете, полягає в тому, щоб ваша програма по-різному діяла на кожному запуску, вищевказане рішення чудово.
Jemenake

238

rand()Функція <stdlib.h>повертає псевдо-випадкове число між 0 і RAND_MAX. Ви можете використовувати srand(unsigned int seed)для встановлення насіння.

Це звичайна практика використовувати %оператора спільно з, rand()щоб отримати інший діапазон (хоча майте на увазі, що це дещо знімає рівномірність). Наприклад:

/* random int between 0 and 19 */
int r = rand() % 20;

Якщо ви насправді дбаєте про рівномірність, ви можете зробити щось подібне:

/* Returns an integer in the range [0, n).
 *
 * Uses rand(), and so is affected-by/affects the same seed.
 */
int randint(int n) {
  if ((n - 1) == RAND_MAX) {
    return rand();
  } else {
    // Supporting larger values for n would requires an even more
    // elaborate implementation that combines multiple calls to rand()
    assert (n <= RAND_MAX)

    // Chop off all of the values that would cause skew...
    int end = RAND_MAX / n; // truncate skew
    assert (end > 0);
    end *= n;

    // ... and ignore results from rand() that fall above that limit.
    // (Worst case the loop condition should succeed 50% of the time,
    // so we can expect to bail out of this loop pretty quickly.)
    int r;
    while ((r = rand()) >= end);

    return r % n;
  }
}

18
Це звичайна практика , але не правильна. Дивіться це і це .
Лазер

35
@Lazer: Тому я сказав "хоча майте на увазі, що це дещо знімає рівномірність".
Лоранс Гонсальвес

3
@AbhimanyuAryan %Оператор модуля. Це дає залишок від ділення без остачі, так що x % nзавжди буде давати вам номер між 0і n - 1( до тих пір , як xі nобидва позитивні). Якщо ви все-таки вважаєте це заплутаним, спробуйте написати програму, яка iнараховує від 0 до 100, і роздрукує i % nпевну nчастину вашого вибору менше 100.
Лоранс Гонсалвс

2
@necromancer Я пішов вперед і додав ідеально рівномірне рішення.
Лоранс Гонсалвс

2
@Lazer друге посилання, яке ви опублікували, насправді все ще не ідеально рівномірне. Кастинг в дубль і назад не допомагає. Перше посилання, яке ви опублікували, має ідеально рівномірне рішення, хоча для невеликих верхніх меж воно буде багато . До цієї відповіді я додав ідеально рівномірне рішення, яке не повинно зациклюватися навіть на невеликих верхніх межах.
Лоранс Гонсалвс

53

Якщо вам потрібні захищені випадкові символи чи цілі числа:

Як розглянуто, як безпечно генерувати випадкові числа на різних мовах програмування , вам потрібно зробити одне з наступних:

Наприклад:

#include "sodium.h"

int foo()
{
    char myString[32];
    uint32_t myInt;

    if (sodium_init() < 0) {
        /* panic! the library couldn't be initialized, it is not safe to use */
        return 1; 
    }


    /* myString will be an array of 32 random bytes, not null-terminated */        
    randombytes_buf(myString, 32);

    /* myInt will be a random number between 0 and 9 */
    myInt = randombytes_uniform(10);
}

randombytes_uniform() є криптографічно захищеним та неупередженим.


чи слід виводити лібсорій RNG перед викликом randombytes_buf?
користувач2199593

Просто зателефонуйте sodium_init()в якийсь момент. Не турбуйтеся про RNG, він використовує ядро.
Скотт Арчішевський

Примітка. Я схвалив останню редакцію, sodium_init()хоча це не обов'язково є частиною мого прикладу, оскільки це важлива деталь.
Скотт Арчішевський

29

Давайте пройдемося через це. Спочатку ми використовуємо функцію srand () для висіву рандомізатора. В основному комп'ютер може генерувати випадкові числа на основі числа, яке подається на srand (). Якби ви дали одне і те ж значення насіння, то кожного разу будуть генеруватися однакові випадкові числа.

Тому нам доведеться викласти рандомізатор зі значенням, яке завжди змінюється. Ми робимо це, подаючи йому значення поточного часу за допомогою функції time ().

Тепер, коли ми викликаємо rand (), кожного разу буде створюватися нове випадкове число.

#include <stdio.h>

int random_number(int min_num, int max_num);

int main(void)
{
    printf("Min : 1 Max : 40 %d\n", random_number(1,40));
    printf("Min : 100 Max : 1000 %d\n",random_number(100,1000));
    return 0;
}

int random_number(int min_num, int max_num)
{
    int result = 0, low_num = 0, hi_num = 0;

    if (min_num < max_num)
    {
        low_num = min_num;
        hi_num = max_num + 1; // include max_num in output
    } else {
        low_num = max_num + 1; // include max_num in output
        hi_num = min_num;
    }

    srand(time(NULL));
    result = (rand() % (hi_num - low_num)) + low_num;
    return result;
}

12
Хороший код, але не дуже гарна ідея називати "srand (час (NULL))"; '. цей метод видає однакове число, коли викликається в циклі for.
RayOldProf

1
Пропоновані зміни, що містять код, часто відхиляються. Хтось зробив тут один із коментарями: "алгоритм помилився. Можливо, вийде більше, ніж максимальне". Я сам не оцінював претензію.
Мартін Сміт

1
@ Мартін Сміт Проблеми: 1) має бути else{ low_num=max_num; hi_num=min_num+1;2) не вдається, коли hi_num - low_num > INT_MAX. 3) Опускає значення в рідкісній ситуації INT_MAX > hi_num - low_num > RAND_MAX.
chux

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

1
Незначні: hi_num = max_num + 1;не вистачає захисту від переливу.
chux

25

Якщо вам потрібні псевдо випадкові числа кращої якості, ніж те, що stdlibпередбачено, перегляньте Mersenne Twister . Це теж швидше. Зразкових реалізацій багато, наприклад, тут .


2
+1: Виглядає круто, але я просто робив гру на здогадки. Якби я збирався використовувати генератор випадкових чисел у діловому додатку, я б точно використав це.
Креднс

4
Не використовуйте Mersenne Twister, використовуйте щось хороше, як xoroshiro128 + або PCG. (Відповідне посилання.)
Ведрак

17

Стандартна функція С є rand(). Це досить добре, щоб розібрати картки для пасьянсу, але це жахливо. Багато реалізацій rand()циклу через короткий список чисел, а низькі біти мають коротші цикли. Те, як називають деякі програми rand(), жахливе, і обчислити хороший насіннєвий прохід srand()важко.

Найкращий спосіб генерувати випадкові числа в C - це використання сторонньої бібліотеки на зразок OpenSSL. Наприклад,

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <openssl/rand.h>

/* Random integer in [0, limit) */
unsigned int random_uint(unsigned int limit) {
    union {
        unsigned int i;
        unsigned char c[sizeof(unsigned int)];
    } u;

    do {
        if (!RAND_bytes(u.c, sizeof(u.c))) {
            fprintf(stderr, "Can't get random bytes!\n");
            exit(1);
        }
    } while (u.i < (-limit % limit)); /* u.i < (2**size % limit) */
    return u.i % limit;
}

/* Random double in [0.0, 1.0) */
double random_double() {
    union {
        uint64_t i;
        unsigned char c[sizeof(uint64_t)];
    } u;

    if (!RAND_bytes(u.c, sizeof(u.c))) {
        fprintf(stderr, "Can't get random bytes!\n");
        exit(1);
    }
    /* 53 bits / 2**53 */
    return (u.i >> 11) * (1.0/9007199254740992.0);
}

int main() {
    printf("Dice: %d\n", (int)(random_uint(6) + 1));
    printf("Double: %f\n", random_double());
    return 0;
}

Чому так багато коду? Інші мови, такі як Java і Ruby, мають функції для випадкових цілих чисел або плавців. OpenSSL видає лише випадкові байти, тому я намагаюся імітувати, як Java або Ruby перетворювали б їх на цілі чи плавці.

Для цілих чисел ми хочемо уникнути зміщення модуля . Припустимо, ми отримали від випадкових чотиризначних цілих чисел rand() % 10000, але rand()можемо повернути лише 0 до 32767 (як це робиться в Microsoft Windows). Кожне число від 0 до 2767 відображатиметься частіше, ніж кожне число від 2768 до 9999. Щоб видалити зміщення, ми можемо повторити спробуrand() поки значення нижче 2768, оскільки значення 30000 від 2768 до 32767 відображаються рівномірно на значення 10000 від 0 до 9999.

Для плавців ми хочемо 53 випадкових біта, тому що doubleтримає 53 біта точності (якщо припустити, що це IEEE подвійний). Якщо ми використовуємо більше 53 біт, ми отримуємо округлення зміщення. Деякі програмісти записують такий код, як rand() / (double)RAND_MAX, але rand()можуть повернути лише 31 біт або лише 15 біт у Windows.

RAND_bytes()Насіння OpenSSL , можливо, читаючи /dev/urandomв Linux. Якщо нам потрібно багато випадкових чисел, було б занадто повільно їх читати /dev/urandom, тому що вони повинні бути скопійовані з ядра. Швидше дозволити OpenSSL генерувати більше випадкових чисел із насіння.

Більше про випадкові числа:

  • Perl_seed () Perl - приклад того, як обчислити насіння в C для srand(). Він змішує біти поточного часу, ідентифікатор процесу та деякі покажчики, якщо він не може прочитати /dev/urandom.
  • Arc4random_uniform () OpenBSD пояснює зміщення модуля.
  • Java API для java.util.Random описує алгоритми для видалення зміщення з випадкових цілих чисел та упаковки 53 біт у випадкові поплавці.

Дякую за розширену відповідь. Зауважте, що з 24 поточних відповідей на це питання ви були єдиним, хто мав зайву інтерпретацію float/ double, тож я уточнив питання, щоб дотримуватися intцифр, щоб не зробити його занадто широким. Є й інші питання C, які стосуються конкретно float/ doubleвипадкових значень, тому ви, можливо, захочете перевстановити свою другу половину своєї відповіді на такі питання, як stackoverflow.com/questions/13408990/…
Cœur

11

Якщо ваша система підтримує arc4randomсімейство функцій, я б рекомендував використовувати їх замість стандартної randфункції.

arc4randomСімейство включає в себе:

uint32_t arc4random(void)
void arc4random_buf(void *buf, size_t bytes)
uint32_t arc4random_uniform(uint32_t limit)
void arc4random_stir(void)
void arc4random_addrandom(unsigned char *dat, int datlen)

arc4random повертає випадкове 32-бітне ціле число, не підписане

arc4random_bufдодає випадковий вміст у його параметр buf : void *. Кількість вмісту визначається bytes : size_tпараметром.

arc4random_uniformповертає випадкове 32-бітове непідписане ціле число, яке слідує правилу: 0 <= arc4random_uniform(limit) < limitде лімітом є також непідписане 32-бітове ціле число.

arc4random_stirзчитує дані /dev/urandomі передає їх, arc4random_addrandomщоб додатково рандомізувати свій внутрішній пул випадкових чисел.

arc4random_addrandomвикористовується arc4random_stirдля заповнення внутрішнього пулу випадкових чисел відповідно до переданих йому даних.

Якщо у вас немає цих функцій, але ви перебуваєте на Unix, ви можете використовувати цей код:

/* This is C, not C++ */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h> /* exit */
#include <stdio.h> /* printf */

int urandom_fd = -2;

void urandom_init() {
  urandom_fd = open("/dev/urandom", O_RDONLY);

  if (urandom_fd == -1) {
    int errsv = urandom_fd;
    printf("Error opening [/dev/urandom]: %i\n", errsv);
    exit(1);
  }
}

unsigned long urandom() {
  unsigned long buf_impl;
  unsigned long *buf = &buf_impl;

  if (urandom_fd == -2) {
    urandom_init();
  }

  /* Read 4 bytes, or 32 bits into *buf, which points to buf_impl */
  read(urandom_fd, buf, sizeof(long));
  return buf_impl;
}

urandom_initФункція відкриває /dev/urandomпристрій, і поміщає дескриптор файлу в urandom_fd.

urandomФункція в основному така ж , як заклик до rand, за винятком більш безпечним, і вона повертаєlong (легко змінюваний).

Однак це /dev/urandomможе бути трохи повільним, тому рекомендується використовувати його як насіння для іншого генератора випадкових чисел.

Якщо ваша система не має /dev/urandom, але дійсно є /dev/randomабо подібний файл, то ви можете просто змінити шлях , пройдений до openв urandom_init. Виклики та API, які використовуються urandom_initта urandomє (я вважаю) сумісними з POSIX, і як такі, повинні працювати на більшості, якщо не на всіх сумісних з POSIX системах.

Примітки. Прочитання від /dev/urandomНЕ блокується, якщо недоступна ентропія, тому значення, створені за таких обставин, можуть бути криптографічно незахищеними. Якщо ви переживаєте з цього приводу, тоді використовуйте /dev/random, яке завжди блокується, якщо недостатня ентропія.

Якщо ви перебуваєте в іншій системі (наприклад, Windows), використовуйте randабо якийсь внутрішній API, що не залежить від платформи, залежний від платформи Windows.

Обгортка функції для urandom, randабо arc4randomвикликів:

#define RAND_IMPL /* urandom(see large code block) | rand | arc4random */

int myRandom(int bottom, int top){
    return (RAND_IMPL() % (top - bottom)) + bottom;
}

8

STL не існує для C. Ви повинні зателефонувати rand, або ще краще, random. Вони задекларовані в стандартному заголовку бібліотеки stdlib.h. randє POSIX,random є функцією специфікації BSD.

Різниця між randі randomполягає в тому, що randomповертає набагато більш зручне 32-бітове випадкове число і, randяк правило, повертає 16-бітове число. Настанови BSD показують, що нижчі біти randциклічні та передбачувані, тому randпотенційно марні для невеликої кількості.


3
@Neil - оскільки всі відповіді досі згадують STL, я підозрюю, що це питання було швидко відредаговано, щоб видалити зайві посилання.
Майкл Берр

rand () не марний для невеликих номерів - ви можете перемістити їх в біт і використовувати лише більш випадкові високі біти, якщо вам це дійсно потрібно.
Кріс Лутц

@Chris, ви можете, якщо розмір випадкового числа відомий, але якщо необхідний розмір випадкового числа змінюється під час виконання (наприклад, перетасування динамічного масиву тощо), важко буде обійти такий застереження.
dreamlax

Я не можу знайти будь-яку випадкову-функцію тут :-(
kasia.b

@ kasia.b у цьому посиланні є extern int rand(void);і extern void srand(unsigned int);.
RastaJedi

7

Подивіться на ISAAC (непрямий, зсув, накопичення, додавання та підрахунок). Він рівномірно розподілений і має середню довжину циклу 2 ^ 8295.


2
ISAAC - це цікавий RNG через свою швидкість, але ще не отримав серйозної криптографічної уваги.
user2398029

4

Це хороший спосіб отримати випадкове число між двома обраними вами числами.

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

    #define randnum(min, max) \
        ((rand() % (int)(((max) + 1) - (min))) + (min))

int main()
{
    srand(time(NULL));

    printf("%d\n", randnum(1, 70));
}

Вихід перший раз: 39

Виведіть другий раз: 61

Виведіть третій раз: 65

Ви можете змінювати значення після того, randnumяк ви обрали будь-які числа, і це створить випадкове число для вас між цими двома числами.


4

Ви хочете використовувати rand(). Примітка ( ДУЖЕ ВАЖЛИВО ): не забудьте встановити насіння для функції rand. Якщо цього не зробити, ваші випадкові числа не є справді випадковими . Це дуже, дуже, дуже важливо. На щастя, зазвичай ви можете використовувати деяку комбінацію таймера системних кліщів та дати, щоб отримати гарне насіння.


6
Два бали: а) ваші випадкові числа не є "справді" випадковими, незалежно від того, як ви насінете генератор. І б) дуже зручно, щоб псевдовипадкова послідовність завжди була однаковою за багатьох обставин - наприклад, для тестування.

16
якщо ДУЖЕ ВАЖЛИВО, щоб ваш номер був справді випадковим, вам не слід використовувати функцію rand ().
tylerl

1
Значення rand зовсім не є "справді" випадковими, незалежно від того, ви встановили насіння чи ні. З огляду на відоме насіння послідовність є передбачуваною. "По-справжньому" генерація випадкових чисел складна. Немає ентропії, пов'язаної з рандом.
dreamlax

2
Звичайно, вони зроблять - генератор засідає для вас бібліотекою (можливо, до нуля, але це дійсне насіння).

4
Так, але відомий алгоритм / відоме насіння є важливим для налагодження будь-якої програми, яка використовує випадкові числа. Незвичайно реєструвати насіння, що використовується, разом із запуском симуляції, щоб його можна було відтворити для більш детального аналізу. Зовсім не виклик srand () еквівалентний виклику srand (1).
RBerteig

4

FWIW, відповідь полягає в тому, що так, є stdlib.hфункція, яка називається rand; ця функція налаштована насамперед на швидкість і розподіл, а не на непередбачуваність. Майже всі вбудовані випадкові функції для різних мов та фреймворків використовують цю функцію за замовчуванням. Існують також "криптографічні" генератори випадкових чисел, які набагато менш передбачувані, але працюють набагато повільніше. Вони повинні використовуватися в будь-якому застосуванні, пов'язаному з безпекою.


4

Сподіваємось, це трохи випадковіше, ніж просто використання srand(time(NULL)).

#include <time.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv)
{
    srand((unsigned int)**main + (unsigned int)&argc + (unsigned int)time(NULL));
    srand(rand());

    for (int i = 0; i < 10; i++)
        printf("%d\n", rand());
}

1
додавання srand (rand ()) не збільшує випадковість послідовності, якщо ця програма виконується кілька разів протягом 1 секунди. час (NULL) все одно поверне однакове значення для кожного з них, перший rand () повернеться однаково довгий, а другий виклик srand () буде з тим самим значенням, в результаті чого все ще буде однакова випадкова послідовність. Використання адреси argc може допомогти, лише якщо гарантується, що ця адреса буде різною при кожному виконанні програми, що не завжди відповідає дійсності.
theferrit32

3

Ну, STL - це C ++, а не C, тому я не знаю, що ви хочете. Якщо ви хочете C, однак, є rand()і srand()функції:

int rand(void);

void srand(unsigned seed);

Вони обидва є частиною ANSI C. Є також random()функція:

long random(void);

Але наскільки я можу сказати, random()це не стандартний ANSI C. Стороння бібліотека може бути не поганою ідеєю, але все залежить від того, наскільки випадковим є число, яке вам потрібно створити.


3

C Програма для генерації випадкових чисел між 9 і 50

#include <time.h>
#include <stdlib.h>

int main()
{
    srand(time(NULL));
    int lowerLimit = 10, upperLimit = 50;
    int r =  lowerLimit + rand() % (upperLimit - lowerLimit);
    printf("%d", r);
}

Взагалі ми можемо генерувати випадкове число між нижчим і нижчим лімітом-1

тобто нижній ліміт включений або скажіть r ∈ [нижній ліміт, верхній ліміт)


@Pang Це я чітко згадав МІЖ 9 та 50, а не 9 та 50.
Шивам К. Тхакар

2
Ваша модульна операція запровадила зміщення.
jww

2

rand() є найзручнішим способом генерації випадкових чисел.

Ви також можете зловити випадкове число з будь-якої інтернет-служби, наприклад random.org.


1
Ви також можете зловити випадкове число з будь-якої інтернет-служби, наприклад random.org Bounty, якщо ви включите портативний, ефективний спосіб зробити це в C.
MD XF

2
#include <stdio.h>
#include <stdlib.h>

void main() 
{
    int visited[100];
    int randValue, a, b, vindex = 0;

    randValue = (rand() % 100) + 1;

    while (vindex < 100) {
        for (b = 0; b < vindex; b++) {
            if (visited[b] == randValue) {
                randValue = (rand() % 100) + 1;
                b = 0;
            }
        }

        visited[vindex++] = randValue;
    }

    for (a = 0; a < 100; a++)
        printf("%d ", visited[a]);
}

потім визначтеся вгорі зі змінними @ b4hand
Мухаммад Садік

На той момент, коли я робив цей коментар, у мене не було універсальних дозволів на редагування, і зазвичай зміни коду, які змінили фактичну відповідь, будуть відхилені. Якщо ви не хочете виправити свою відповідь, я можу, хоча.
b4hand

Якщо ваш намір полягав у створенні випадкової перестановки унікальних значень, цей код все ще має помилку. Він видає значення 84 вдвічі і не видає значення 48. Крім того, він не закладає генератор випадкових чисел, тому послідовність однакова для кожного виконання.
b4hand

1
змінні a і b визначені в цьому коді. Він не містить помилок .. немає і синтаксису, і логічної помилки.
Мухаммед Садік

Неправильно. Я вже вручну підтвердив вихід, як я вже згадував.
b4hand

1
#include <stdio.h>
#include <dos.h>

int random(int range);

int main(void)
{
    printf("%d", random(10));
    return 0;
}

int random(int range)
{
    struct time t;
    int r;

    gettime(&t);
    r = t.ti_sec % range;
    return r;
}

1

У сучасних процесорах x86_64 ви можете використовувати апаратний генератор випадкових чисел через _rdrand64_step()

Приклад коду:

#include <immintrin.h>

uint64_t randVal;
if(!_rdrand64_step(&randVal)) {
  // Report an error here: random number generation has failed!
}
// If no error occured, randVal contains a random 64-bit number

1
#include<stdio.h>
#include<stdlib.h>
#include<time.h>

//generate number in range [min,max)
int random(int min, int max){
    int number = min + rand() % (max - min);
    return number; 
}

//Driver code
int main(){
    srand(time(NULL));
    for(int i = 1; i <= 10; i++){
        printf("%d\t", random(10, 100));
    }
    return 0;
}

0

Почувши гарне пояснення того, чому використовувати rand()для отримання рівномірно розподілених випадкових чисел у заданому діапазоні є поганою ідеєю, я вирішив поглянути на те, наскільки насправді є вихідний результат. Мій тестовий випадок був справедливим метанням кісток. Ось код C:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(int argc, char *argv[])
{
    int i;
    int dice[6];

    for (i = 0; i < 6; i++) 
      dice[i] = 0;
    srand(time(NULL));

    const int TOTAL = 10000000;
    for (i = 0; i < TOTAL; i++)
      dice[(rand() % 6)] += 1;

    double pers = 0.0, tpers = 0.0;
    for (i = 0; i < 6; i++) {
      pers = (dice[i] * 100.0) / TOTAL;
      printf("\t%1d  %5.2f%%\n", dice[i], pers);
      tpers += pers;
    }
    printf("\ttotal:  %6.2f%%\n", tpers);
}

і ось його результат:

 $ gcc -o t3 t3.c
 $ ./t3 
        1666598  16.67%     
        1668630  16.69%
        1667682  16.68%
        1666049  16.66%
        1665948  16.66%
        1665093  16.65%
        total:  100.00%
 $ ./t3     
        1667634  16.68%
        1665914  16.66%
        1665542  16.66%
        1667828  16.68%
        1663649  16.64%
        1669433  16.69%
        total:  100.00%

Я не знаю, наскільки рівномірними вам потрібні випадкові числа, але вищезазначене виглядає досить рівномірним для більшості потреб.

Редагувати: було б корисно ініціалізувати PRNG з чимось кращим, ніж time(NULL).


rand () може провалити інші тести на випадковість, наприклад, тверді тести . rand () відрізняється від платформи до платформи; значення rand () від GNU / Linux можуть бути кращими за значення BSD або Windows.
Джордж Келер

Це неправильний спосіб перевірки на випадковість.
Ведрак

Залежить від мети та моделі загрози / ризику. Для криптографічно сильних RNG - обов'язково використовуйте RDRAND (або RDSEED). Для простого метальника для кісток (не на рівні казино) IMHO вищезазначеного повинно вистачити. Ключове слово " досить добре ".
Миша

0

У моїй недавній програмі у мене виник серйозна проблема з генератором псевдовипадкових чисел: я повторно називав свою програму C через сценарій pyhton, і я використовував як насіння наступний код:

srand(time(NULL))

Однак, оскільки:

  • rand генерує ту саму псевдо випадкову послідовність і дасть одне насіння в srand (див man srand );
  • Як уже було сказано, функція часу змінюється лише секунду з секунди: якщо ваша програма запускається кілька разів протягом однієї секунди, timeкожен раз буде повертати одне і те ж значення.

Моя програма генерувала однакову послідовність чисел. Ви можете зробити 3 речі для вирішення цієї проблеми:

  1. змішати час виводу з іншою інформацією, що змінюється на прогонах (у моєму додатку назва виводу):

    srand(time(NULL) | getHashOfString(outputName))

    Я використовував djb2 як свою хеш-функцію.

  2. Збільшити дозвіл часу. На моїй платформі clock_gettimeбув доступний, тому я використовую її:

    #include<time.h>
    struct timespec nanos;
    clock_gettime(CLOCK_MONOTONIC, &nanos)
    srand(nanos.tv_nsec);
  3. Використовуйте обидва способи разом:

    #include<time.h>
    struct timespec nanos;
    clock_gettime(CLOCK_MONOTONIC, &nanos)
    srand(nanos.tv_nsec | getHashOfString(outputName));

Варіант 3 забезпечує вам (наскільки я знаю) найкращу випадковість насіння, але це може створити різницю лише при дуже швидкому застосуванні. На мою думку, варіант 2 - це безпечна ставка.


Навіть з цією евристикою не покладайтеся на rand () для криптографічних даних.
domenukk

rand()не слід використовувати для криптографічних даних, я згоден. Принаймні для мене моя програма не передбачала криптографічних даних, тому для мене це було нормально даним методом.
Колдар

0

Незважаючи на всі пропозиції людей rand()тут, ви не хочете користуватися, rand()якщо вам не доведеться! Випадкові числа, які rand()виробляють, часто дуже погані. Процитуйте зі сторінки чоловіка Linux:

У версіях rand()і srand()в бібліотеці Linux C використовується той же генератор випадкових чисел, що random(3)і srandom(3), тому біти нижчого порядку повинні бути такими ж випадковими, як і біти вищого порядку. Однак у старих реалізаціях rand () та в поточних реалізаціях в різних системах біти нижчого порядку набагато менш випадкові, ніж біти вищого порядку . Не використовуйте цю функцію в додатках, призначених для переносності, коли потрібна хороша випадковість. ( Використовуйте random(3)замість цього. )

Що стосується портативності, random()вона також визначається стандартом POSIX вже досить давно. rand()старше, він з'явився вже в першій специфікації POSIX.1 (IEEE Std 1003.1-1988), тоді як random()вперше з'явився в POSIX.1-2001 (IEEE Std 1003.1-2001), але поточний стандарт POSIX вже є POSIX.1-2008 (IEEE Std 1003.1-2008), який отримав оновлення лише рік тому (IEEE Std 1003.1-2008, Edition 2016). Тож я б вважав random()дуже портативним.

POSIX.1-2001 також запровадив lrand48()і mrand48()функції, дивіться тут :

Це сімейство функцій повинно генерувати псевдовипадкові числа, використовуючи лінійний конгрурентний алгоритм та 48-бітну цілу арифметику.

І досить хорошим псевдо випадковим джерелом є arc4random()функція, яка доступна у багатьох системах. Не входить до жодного офіційного стандарту, що з'явився в BSD близько 1997 року, але його можна знайти в таких системах, як Linux та macOS / iOS.


random()не існує в Windows.
Бьорн Ліндквіст

@ BjörnLindqvist Windows також немає системи POSIX; це майже єдина система на ринку, яка не підтримує принаймні базових API POSIX (які навіть заблоковані системи, такі як iOS підтримують). Windows підтримує лише те, що rand()цього вимагає і стандарт C. Для всього іншого вам потрібно спеціальне рішення лише для Windows, як і зазвичай. #ifdef _WIN32- це фраза, яку ви бачите найчастіше в кросплатформенному коді, який хоче підтримувати Windows, а зазвичай є одне рішення, яке працює з усіма системами, і таке, яке потрібно лише для Windows.
Мецьки

0

Для додатків Linux C:

Це мій перероблений код з відповіді вище, яка відповідає моїй практиці з кодом С і повертає випадковий буфер будь-якого розміру (з належними кодами повернення тощо). Обов’язково телефонуйте urandom_open()один раз на початку програми.

int gUrandomFd = -1;

int urandom_open(void)
{
    if (gUrandomFd == -1) {
        gUrandomFd = open("/dev/urandom", O_RDONLY);
    }

    if (gUrandomFd == -1) {
        fprintf(stderr, "Error opening /dev/urandom: errno [%d], strerrer [%s]\n",
                  errno, strerror(errno));
        return -1;
    } else {
        return 0;
    }
}


void urandom_close(void)
{
    close(gUrandomFd);
    gUrandomFd = -1;
}


//
// This link essentially validates the merits of /dev/urandom:
// http://sockpuppet.org/blog/2014/02/25/safely-generate-random-numbers/
//
int getRandomBuffer(uint8_t *buf, int size)
{
    int ret = 0; // Return value

    if (gUrandomFd == -1) {
        fprintf(stderr, "Urandom (/dev/urandom) file not open\n");
        return -1;
    }

    ret = read(gUrandomFd, buf, size);

    if (ret != size) {
        fprintf(stderr, "Only read [%d] bytes, expected [%d]\n",
                 ret, size);
        return -1;
    } else {
        return 0;
    }
}

-2

Моє мінімалістичне рішення повинно працювати для випадкових чисел у діапазоні [min, max). Використовуйте srand(time(NULL))перед викликом функції.

int range_rand(int min_num, int max_num) {
    if (min_num >= max_num) {
        fprintf(stderr, "min_num is greater or equal than max_num!\n"); 
    }
    return min_num + (rand() % (max_num - min_num));
} 

-3

Спробуйте це, я зібрав це з деяких понять, на які вже згадувалося вище:

/*    
Uses the srand() function to seed the random number generator based on time value,
then returns an integer in the range 1 to max. Call this with random(n) where n is an integer, and you get an integer as a return value.
 */

int random(int max) {
    srand((unsigned) time(NULL));
    return (rand() % max) + 1;
}

16
Цей код не годиться. Телефонувати srand()кожен раз, коли ви хочете зателефонувати rand()- це жахлива ідея. Оскільки time()зазвичай повертає значення за секунди, швидко викликаючи цю функцію, повернеться те саме "випадкове" значення.
Blastfurnace

3
Цю функцію плутатимуть із random()функцією Unix .
Джордж Келер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.