Генерация случайных чисел в языке Си
Иногда может возникнуть необходимость в генерации случайных чисел. Простой пример.
Пример: Программа для определения победитя в конкурсе репостов.
В конкурсе репостов приняли участие 100 человек. Среди них необходимо случайным образом выбрать победителя.
Если вы выберете его самостоятельно, то вас могут обвинить в предвзятости. Чтобы этого не произошло, вы решили написать программу, которая будет работать следующим образом: пользователь вводит количество участников N
, после чего программа выводит одно случайное число от 1
до N
включительно — номер победителя.
Как получить число от пользователя, вам уже известно. А вот как заставить компьютер загадать случайное число? В этом уроке вы этому научитесь.
Функция rand()
Функция rand
возвращает случайное целое число в диапазоне [0, RAND_MAX]
(от нуля до RAND_MAX
). RAND_MAX
— это специальная константа языка Си, в которой записано наибольшее целое число, которое может быть возвращено функцией rand
.
Функция rand
определена в заголовочном файле stdlib.h
. Поэтому, чтобы использовать rand
в программе, не забудьте подключить этот заголовочный файл. Константа RAND_MAX
тоже определена в этом файле. Вы можете найти этот файл у себя на компьютере и посмотреть её значение.
Давайте посмотрим на эту функцию в действии. Запустим следующий код:
Листинг 1.
#include <stdio.h> // чтобы пользоваться функцией printf
#include <stdlib.h> // чтобы пользоваться функцией rand
int main(void)
{
// генерируем пять случайных целых чисел
printf("%d\n", rand());
printf("%d\n", rand());
printf("%d\n", rand());
printf("%d\n", rand());
printf("%d\n", rand());
return 0;
}
Должно получиться что-то вроде этого.

Рис.1 Пять случайных чисел, сгенерированных функцией rand
Но нам бы хотелось получить числа от 1
до 100
, а не всё подряд.
Ниже описано несколько приёмов, позволяющих наложить ограничения на функцию rand
.
Ограничить диапазон генератора случайных чисел сверху (справа)
Кто в школе ждал момента, когда ему пригодится математика, приготовьтесь. Этот момент наступил. Чтобы ограничить сверху случайные числа, можно воспользоваться операцией получения остатка от деления, которую мы разбирали в прошлом уроке.
Думаю, вы знаете, что остаток от деления на число K
всегда меньше самого числа K
. Например, при делении положительного целого числа на 4
могут получиться остатки 0
, 1
, 2
и 3
.
Трюк
Поэтому, если вы хотите ограничить сверху диапазон генерируемых случайных чисел значением K
, то просто возьмите остаток от деления на K + 1
: rand() % (K + 1)
.
Единичку прибавлять обязательно, иначе сам число K
никогда не будет сгенерировано.
Листинг 2.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
// генерируем пять случайных целых чисел <= 100
printf("%d\n", rand() % 101);
printf("%d\n", rand() % 101);
printf("%d\n", rand() % 101);
printf("%d\n", rand() % 101);
printf("%d\n", rand() % 101);
return 0;
}

Рис. 2 Пять случайных чисел не превышающих 100
Ограничить числа снизу
Функция rand
возвращает случайные числа из отрезка [0, RAND_MAX]
. А что если нам нужны только числа большие числа M
(например, 50
)? Как быть? Всё просто. Давайте прибавим к тому, что вернула функция rand
, наше значение M
. Тогда, если функция вернёт 0
, итоговый ответ будет M
, если 2394
, то итоговый ответ будет M + 2394
. Этим действием мы как бы сдвигаем все числа на M
единиц вперёд.
Трюк
Если вы хотите ограничить диапазон чисел, генерируемых функцией rand
, снизу (слева) числом M
, то используйте формулу M + rand()
Задать границы функции rand сверху и снизу
Теперь попробуем получить числа из диапазона [80, 100]
. Кажется, что достаточно просто объединить два способа, которые приведены выше. Давайте так и сделаем. Получим что-то вроде этого:
Листинг 3.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
/* генерируем пять случайных целых чисел
больше 80 и меньших 100 */
printf("%d\n", 80 + rand() % 101);
printf("%d\n", 80 + rand() % 101);
printf("%d\n", 80 + rand() % 101);
printf("%d\n", 80 + rand() % 101);
printf("%d\n", 80 + rand() % 101);
return 0;
}
Попробуйте запустить эту программу. Удивлены?
Да, такой способ работать не будет. Давайте рассмотрим следующий пример, чтобы понять, где мы допустили ошибку.
Допустим rand
вернула число 143
. Остаток от деления 143
на 101
равен 42
. Дальше 80 + 42 = 122
, что больше верхнего ограничения 100
Значит такой способ не работает. Подобная конструкция выдаст числа от 80
до 180
.
Давайте разберём по действиям выражение 80 + rand() % 101
.
rand() % 101
может выдать числа от0
до100
включительно. Т.е. из отрезка[0, 100]
.- Добавя к диапазону
80
сдвигает наш отрезок на80
единиц вправо. Получаем[80, 180]
.
Как видим, проблема у нас заключается в правой границе отрезка, она сдвинута вправо на 80
единиц. Это как раз число 80
, которое мы прибавляем, чтобы сдвинуть нижнюю (левую) границу отрезка. Но добавлении числа сдвигает обе границы и нижнюю (левую) и верхнюю (правую). Давайте наведём порядок и сдвинем правую границу назад: 80 + rand()%(101 - 80)
. Теперь всё должно работать, как надо.
Трюк
Если нужно получить числа из отрезка [A, B]
, то можно воспользоваться следующей конструкцией:
A + rand() % (B - A + 1)
Используя этот трюк перепишем программу из Листинга 3:
Листинг 4.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
/* генерируем пять случайных целых чисел
из отрезка [80, 100] */
printf("%d\n", 80 + rand() % (100 - 80 + 1));
printf("%d\n", 80 + rand() % (100 - 79));
printf("%d\n", 80 + rand() % 21);
printf("%d\n", 80 + rand() % 21);
printf("%d\n", 80 + rand() % 21);
return 0;
}
Результат работы:
![Случайные числа из диапазона \[80, 100\]](https://youngcoder.ru/materials/lang_c/4/3__sluchainie_chisla_na_c/rand_a_b.png)
Случайные числа из диапазона [80, 100]
Ну вот, теперь вы можете решить исходную задачу урока. Сгенерировать число из отрезка [1, N]
.
Хотя нет, всё-таки не можете. Запустите последнюю программу (Листинг 4) три раза подряд и записывайте себе случайные числа, которые она генерирует. Заметили проблему?
Функция srand.
Да, каждый раз появляются одни и те же одинаковые числа. «Так себе генератор!» – скажете вы. И будете не совсем правы. Действительно, генерируются всё время одинаковые числа. Но мы можем на это повлиять, для этого используется функция srand
, которая также определена в заголовочном файле stdlib.h
. Она инициализирует генератор случайных чисел начальным числом.
Скомпилируйте и запустите несколько раз следующую программу:
Листинг 5.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
srand(2);
/* генерируем пять случайных целых чисел из отрезка [80, 100] */
printf("%d\n", 80 + rand()%(100 - 80 + 1));
printf("%d\n", 80 + rand()%(100 - 79));
printf("%d\n", 80 + rand()%21);
printf("%d\n", 80 + rand()%21);
printf("%d\n", 80 + rand()%21);
return 0;
}
Теперь поменяйте аргумент функции srand
на другое число (надеюсь вы ещё не забыли, что такое аргумент функции?) и снова скомпилируйте и запустите программу. Последовательность чисел должна измениться.
Как только мы меняем аргумент в функции srand
-– меняется и последовательность. Не очень практично, не правда ли? Чтобы изменить последовательность, нужно перекомпилировать программу. Вот бы это число менялось автоматически.
И это можно устроить. Например, воспользуемся функцией time
, которая определена в заголовочном файле time.h
. Если в функцию time
в качестве аргумента передать NULL
, то она вернёт количество секунд, прошедших c 1 января 1970 года, а значит аргумент функции srand
будет каждую секунду разный. Вот, посмотрите, как это делается.
Листинг 6.
#include <stdio.h>
#include <stdlib.h>
#include <time.h> // чтобы использовать функцию time()
int main(void)
{
srand(time(NULL));
/* генерируем пять случайных целых чисел из отрезка [80, 100] */
printf("%d\n", 80 + rand()%(100 - 80 + 1));
printf("%d\n", 80 + rand()%(100 - 79));
printf("%d\n", 80 + rand()%21);
printf("%d\n", 80 + rand()%21);
printf("%d\n", 80 + rand()%21);
return 0;
}
Вы спросите, а что такое NULL
? Резонный вопрос. А я вам пока отвечу, что это такое специальное зарезервированное слово. Могу ещё сказать, что им обозначают нулевой указатель, но т.к. это для вас никакой информации не несёт, то на данный момент рекомендую об этом не думать. А просто запомнить как некоторый хитрый трюк. В будущих уроках мы остановимся на этой штуке поподробнее.
Вот теперь вы готовы написать программу для определения победителя в конкурсе репостов.
Практика
Исследовательские задачи для хакеров:
В каких ситуациях ещё может пригодиться генерация случайных чисел? Напишите ваши варианты в комментарии к этому уроку.
Напишите программу, которая выводит на экран значение целочисленной константы
RAND_MAX
. Найдите файлstdlib.h
на вашем компьютере, найдите значение этой константы в этом файле.Найдите в интернете описание функций, которые определены в заголовочном файле
time.h
Вы, конечно, ещё не сможете ими пользоваться, но знать, что такие функции есть, всё равно нужно. Ведь когда-то настанет момент, когда ваших знаний будет достаточно для их использования.Числа, генерируемые функцией
rand
, имеют равномерное распределение. Это значит, что если запускать функциюrand
очень много раз и каждый раз записывать, какое число выпало, то количество выпадения различных чисел будет примерно одинаковым. Например, если генерировать только числа0
и1
, то через100
запусков примерно50
раз выпадет ноль и50
раз единичка. Обратите внимание, что я говорю примерно. Может быть, например,49
и51
, или53
и47
. Если рассматривать это в отношении к общему числу запусков, получим (49/100
и51/100
или53/100
и47/100
соответственно). Но чем больше экспериментов мы проведём, тем ближе отношение количества единичек к количеству испытаний будет стремиться к1/2
. Проведите самостоятельно эксперимент с10
,50
и100
запусками. Это муторно и долго, если делать руками, но что поделать? В будущем мы напишем программу, чтобы проверить свойство равномерности распределения генерируемых случайных чисел.
Дополнительные материалы
Другие функции, объявленные в заголовочном файле
stdlib.h
Хотя я и употребляю везде словосочетание случайные числа, но на самом деле получить действительно случайные числа — сложная задача. Поэтому правильнее было бы назвать полученные числа псевдослучайными. Подробнее об этом можно прочитать здесь.
Если не терпится узнать хоть что-то про
NULL
, то почитайте вот этот урок.Дата 1 января 1970 года особенная. С неё начинается отсчёт эры UNIX. Подробнее об этом и проблемах, которые нас ожидают.