it-swarm.com.ru

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

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

482
Kredns

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

Edit: В Linux вы можете использовать random и srandom .

596
Łukasz Lew

Функция 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;
  }
}
216
Laurence Gonsalves

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

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

Например:

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

43
Scott Arciszewski

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

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

25
MH114

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

Для целых чисел мы хотим избежать modulo bias. Предположим, что мы получили несколько случайных 4-значных целых чисел из Rand() % 10000, но Rand() может вернуть только от 0 до 32767 (как это происходит в Microsoft Windows). Каждое число от 0 до 2767 будет появляться чаще, чем каждое число от 2768 до 9999. Чтобы устранить смещение, мы можем повторить Rand(), когда значение ниже 2768, потому что 30000 значений от 2768 до 32767 отображаются равномерно на 10000 значений от 0 до 9999.

Для чисел с плавающей запятой мы хотим 53 случайных бита, потому что double содержит 53 бита точности (при условии, что это IEEE double). Если мы используем более 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.
  • Arc4random_uniform () в OpenBSD объясняет смещение по модулю.
  • Java API для Java.util.Random описывает алгоритмы для удаления смещения из случайных целых чисел и упаковки 53 битов в случайные числа с плавающей точкой.
16
George Koehler

Если ваша система поддерживает семейство функций 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, где 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;
}
9
Élektra

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

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

8
dreamlax

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

7
geofftnz

Вы хотите использовать Rand(). Примечание (ОЧЕНЬ ВАЖНО): обязательно установите начальное значение для функции Rand. Если вы этого не сделаете, ваши случайные числа не являются действительно случайными. Это очень, очень, очень важно. К счастью, вы можете использовать некоторую комбинацию таймера системных тиков и даты, чтобы получить хорошее начальное число. 

4
Paul Sonier

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

4
tylerl

Надеюсь, это немного более случайно, чем просто использование 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());
}
4
kenorb

Ну, STL - это C++, а не C, поэтому я не знаю, что вы хотите. Однако, если вы хотите C, есть функции Rand() и srand():

int Rand(void);

void srand(unsigned seed);

Они оба являются частью ANSI C. Существует также функция random():

long random(void);

Но, насколько я могу судить, random() не является стандартным ANSI C. Сторонняя библиотека может быть неплохой идеей, но все зависит от того, насколько случайным является число, которое вам действительно нужно сгенерировать.

3
Chris Lutz

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);
}

В общем, мы можем генерировать случайное число между lowerLimit и upperLimit-1  

то есть lowerLimit включительно или, скажем, r ∈ [lowerLimit, upperLimit)  

3
Shivam K Thakkar

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

#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 на любые выбранные вами числа, и они будут генерировать для вас случайное число между этими двумя числами.

3
coderperson

Rand() - наиболее удобный способ генерирования случайных чисел.

Вы также можете поймать случайное число с любого онлайн-сервиса, например random.org.

2
Namit Sinha
#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]);
}
2
Muhammad Sadiq

На современных процессорах 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
Serge Rogatch
#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
Bisu vs Utsab

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

0
Koldar

Несмотря на все предложения людей 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 1003.1-2008), который получил обновление всего год назад (стандарт IEEE 1003.1-2008, издание 2016 г.). Поэтому я считаю random() очень переносимым.

POSIX.1-2001 также представил функции lrand48() и mrand48(), см. Здесь

Это семейство функций должно генерировать псевдослучайные числа с использованием линейного конгруэнтного алгоритма и 48-разрядной целочисленной арифметики.

И довольно хорошим псевдослучайным источником является функция arc4random(), которая доступна во многих системах. Не является частью какого-либо официального стандарта, появившегося в BSD в 1997 году, но вы можете найти его в таких системах, как Linux и macOS/iOS.

0
Mecki

Услышав хорошее объяснение того, почему использование 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).

0
Mouse