Как сгенерировать случайный int в C? - PullRequest
510 голосов
/ 05 мая 2009

Есть ли функция для генерации случайного целого числа в C? Или мне придется использовать стороннюю библиотеку?

Ответы [ 24 ]

625 голосов
/ 05 мая 2009

Примечание : не используйте 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 вы можете использовать random и srandom .

224 голосов
/ 05 мая 2009

Функция 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;
  }
}
48 голосов
/ 13 сентября 2016

Если вам нужны безопасные случайные символы или целые числа:

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

Например:

#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() криптографически безопасен и беспристрастен.

29 голосов
/ 30 января 2013

Давайте пройдем через это. Сначала мы используем функцию 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;
}
25 голосов
/ 31 мая 2009

Если вам нужны псевдослучайные числа лучшего качества, чем то, что дает stdlib, посмотрите Mersenne Twister . Это тоже быстрее. Примеры реализации многочисленны, например здесь .

16 голосов
/ 08 июля 2015

Стандартная функция C - 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 преобразуют их в целые числа или числа с плавающей запятой.

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

OpenSSL RAND_bytes() сам зарождается, возможно, читая /dev/urandom в Linux. Если нам нужно много случайных чисел, было бы слишком медленно читать их все из /dev/urandom, потому что они должны быть скопированы из ядра. Позволяет OpenSSL генерировать больше случайных чисел из начального числа.

Подробнее о случайных числах:

  • Perl's Perl_seed () является примером того, как рассчитать начальное число в C для srand(). Он смешивает биты из текущего времени, идентификатора процесса и некоторых указателей, если не может прочитать /dev/urandom.
  • В OpenBSD arc4random_uniform () объясняет смещение по модулю.
  • Java API для java.util.Random описывает алгоритмы для удаления смещения из случайных целых чисел и упаковки 53 битов в случайные числа с плавающей запятой.
9 голосов
/ 09 августа 2015

Если ваша система поддерживает семейство функций 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-интерфейс, зависящий от платформы.

Функция обертки для вызовов 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 голосов
/ 05 мая 2009

STL не существует для C. Вы должны позвонить rand, или еще лучше, random. Они объявлены в заголовке стандартной библиотеки stdlib.h. rand - это POSIX, random - это спецификация BSD.

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

7 голосов
/ 05 мая 2009

Посмотрите на ISAAC (косвенное, смещение, накопление, сложение и подсчет). Он равномерно распределен и имеет среднюю продолжительность цикла 2 ^ 8295.

4 голосов
/ 05 мая 2009

FWIW, ответ таков: есть функция stdlib.h с именем rand; эта функция настроена в первую очередь на скорость и распределение, а не на непредсказуемость. Почти все встроенные случайные функции для различных языков и сред используют эту функцию по умолчанию. Есть также «криптографические» генераторы случайных чисел, которые гораздо менее предсказуемы, но работают намного медленнее. Их следует использовать в любых приложениях, связанных с безопасностью.

...