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


549

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


1
Длинная тема в comp.lang.c о rand () и PRNG "качество" .
Люсер Дрог

Ответы:


663

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


194
+1 для простоты, но, вероятно, стоит подчеркнуть, что srand () вызывается только один раз . Кроме того, в многопоточном приложении вы можете убедиться, что состояние генератора сохраняется для каждого потока, и заполнить генератор один раз для каждого потока.
RBerteig

41
@trusktr, это сложно. Вот причина: time()меняется только один раз в секунду. Если вы начнете с time()каждого вызова rand(), вы получите одинаковое значение для каждого вызова в течение одной секунды. Но главная причина в том, что свойства rand()и функции, подобные этим, известны лучше всего для случая использования, когда они засеваются ровно один раз за цикл, а не при каждом вызове. Зависимость от «случайности» с непроверенными или недоказанными свойствами приводит к неприятностям.
RBerteig

9
@trusktr для простого линейного конгруэнтного генератора (который rand()обычно и есть), с которым посев с rand()в лучшем случае не будет иметь никакого эффекта вообще, а в худшем разрушит известные качества генератора. Это глубокий предмет. Начните с чтения Кнута Том 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
Это обычная практика , но не правильная. Смотрите это и это .
Lazer

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


должен ли libsodium RNG быть посеян перед вызовом randombytes_buf?
user2199593

Просто позвоните 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 (time (NULL)); этот метод выдает тот же номер при вызове в цикле for.
RayOldProf

1
Предлагаемые правки, связанные с кодом, часто отклоняются. Кто-то сделал это здесь с комментарием «алгоритм был неправильным. Мог произвести большее число, чем максимальное». Сам не оценивал претензию.
Мартин Смит

1
Проблемы @Martin Smith: 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. (Соответствующая ссылка.)
Veedrac

17

Стандартная функция 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 случайных бита, потому что a doubleсодержит 53 бита точности (при условии, что это IEEE double). Если мы используем более 53 бит, мы получаем смещение округления. Некоторые программисты пишут подобный код rand() / (double)RAND_MAX, но rand()могут возвращать только 31 бит или только 15 бит в Windows.

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

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

  • Perl Perl_seed () является примером того, как рассчитать начальное число в Си для 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.

Обертка функции для 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 () не бесполезен для небольших чисел - вы можете сдвинуть их и использовать только более случайные старшие биты, если вам это действительно нужно.
Крис Латс

@ Крис, вы можете, если размер случайного числа известен, но если требуемый размер случайного числа изменяется во время выполнения (например, перетасовывание динамического массива и т. Д.), Будет трудно обойти такую ​​оговорку.
Dreamlax

Я не могу найти любую случайную-функцию здесь :-(
kasia.b

@ kasia.b в этой ссылке есть extern int rand(void);и extern void srand(unsigned int);.
RastaJedi

7

Посмотрите на ISAAC (Indirection, Shift, Accumulate, Add и Count). Он равномерно распределен и имеет среднюю продолжительность цикла 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(). Примечание ( ОЧЕНЬ ВАЖНО ): обязательно установите начальное значение для функции ранда. Если вы этого не сделаете, ваши случайные числа не являются действительно случайными . Это очень, очень, очень важно. К счастью, вы можете использовать некоторую комбинацию таймера системных тиков и даты, чтобы получить хорошее начальное число.


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 секунды. time (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);
}

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

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


@Pang Это то, что я четко упомянул между 9 и 50, а не из 9 и 50.
Шивам К. Таккар

2
Ваша операция по модулю привела к предвзятости.
17

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.
Джордж Келер

Это недопустимый способ проверки на случайность.
Veedrac

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

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 () для криптографических данных.
Доменюк

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, издание 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:

Это мой переработанный код из приведенного выше ответа, который следует моим практикам кода 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()обычно возвращает значение в секундах, быстрый вызов этой функции вернет то же «случайное» значение.
Доменная печь

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