Случайные числа в языке C
Функции rand и srand
В языках программирования обычно предусмотрены функции, позволяющие генерировать случайные числа в определенном по умолчанию диапазоне. На самом деле генерируются не случайные, а так называемые псевдослучайные числа; они выглядят случайно, но вычисляются по вполне конкретной формуле. Далее для простоты мы все равно будем называть их случайными.
В языке программирования C получить случайное число можно с помощью функции rand()
, которая входит в стандартную библиотеку языка. Эта функция не принимает никакие параметры.
#include <stdio.h> #include <stdlib.h> int main() { int a = rand(); printf("%d\n", a); }
Функция rand()
возвращает целое число от 0 до значения присвоенного константе RAND_MAX
. Значение RAND_MAX
зависит от системы и определено в заголовочном файле stdlib.h. Так, например, оно может быть равно 32767 (двухбайтовое целое) или 2147483647 (четырехбайтовое целое).
int a = RAND_MAX; printf("%d\n", a); // 2147483647
Код ниже выводит на экран 50 случайных чисел:
for (int i = 1; i <= 50; i++) { printf("%15d", rand()); if (i % 5 == 0) printf("\n"); }
В теле цикла осуществляется переход на новую строку после каждых выведенных на экран пяти чисел. Для этого используется выражение, в котором находится остаток от деления i на 5, результат сравнивается с 0. Чтобы после первого числа не происходил переход на новую строку, i сначала присваивается единица, а не ноль (т.к. 0 делится на 5 без остатка).
При выполнении программы с таким кодом пятьдесят чисел будут разными. Однако если запустить программу снова, набор чисел окажется такими же, как в предыдущем выполнении. Даже если вы перекомпилируете программу, результат не изменится. Данный эффект связан с тем, что начальное (инициализирующее) число, которое подставляется в формулу вычисления первого и последующих псевдослучайных чисел, для каждой системы всегда одно и то же. Однако это начальное число можно изменить с помощью функции srand()
, которой в качестве параметра передается любое целое число. Понятно, что если вы зададите конкретный аргумент для функции, например, srand(1000)
, то от вызова к вызову программы числа будут также одни и те же. Хотя и не те, что были бы без srand()
. Поэтому появляется проблема, как сделать так, чтобы аргумент для srand()
был тоже случайным? Получается замкнутый круг.
Инициирующее значение можно запрашивать у пользователя с помощью scanf()
и передавать его в srand()
.
scanf("%d", &n); srand(n);
Однако чаще всего это не является полноценным выходом из ситуации. Поэтому инициализирующее значение привязывают к какому-либо процессу, протекающему в операционной системе, например, к часам. Время (учитывая не только время суток, но и дату) никогда не бывает одинаковым. Значит значение для srand()
, преобразованное в целое из системного времени, будет различным.
Текущее время можно узнать с помощью функции time()
, прототип которой описан в файле time.h. Передав time()
в качестве параметра NULL
, мы получим целое число, которое можно передать в srand()
:
srand(time(NULL));
Получение целых случайных чисел в заданных диапазонах
Функция rand()
выдает случайное число от 0 до значения RAND_MAX
. Что делать, если требуется получать случайные числа в иных диапазонах, например, от 100 до 999?
Сначала рассмотрим более простую ситуацию: получить случайные числа от 0 до 5. Если любое целое число попытаться разделить на 5 нацело, то в качестве остатка можно получить как 0 (когда число делится на 5 без остатка), так и 1, 2, 3, 4. Например, rand()
вернула число 283. Применяя к этому числу операцию нахождения остатка от деления на 5, получим 3. Т.е. выражение rand() % 5
дает любое число в диапазоне [0, 5).
Однако, что если надо, чтобы число 5 так же входило в диапазон, т.е. диапазон имеет вид [0, 5]? Логично предположить, что следует найти остаток от деления на 6. При этом более грамотным будет следующее рассуждение: надо находить остаток от деления на размер диапазона. В данном случае он равен шести значениям: 0, 1, 2, 3, 4, 5. Чтобы найти размер диапазона, надо из допустимого максимума вычесть допустимый минимум и прибавить единицу: max - min + 1. Будьте внимательны: если, например, требуется, чтобы указанный в задаче максимум не входил в диапазон, то единицу прибавлять не надо или надо вычитать единицу из максимума.
Следующее выражение выведет на экране случайное число от 0 до 99 включительно:
printf("%d\n", rand() % 100);
Итак, мы знаем формулу получения длины диапазона: max - min + 1. Если требуется получить число от 6 до 10 включительно, то длина диапазона будет равна 10 - 6 + 1 = 5. Выражение rand()% 5
даст любое число от 0 до 4 включительно. Но нам надо от 6 до 10. В таком случае достаточно к полученному случайному остатку прибавить 6, т.е. минимум. Другими словами, надо выполнить сдвиг. Действительно, для приведенного примера:
- если остаток был равен 0, то добавляя 6, получаем 6;
- остаток 1, добавляем 6, получаем 7;
- …
- остаток 4, прибавляем 6, получаем 10;
- остатка больше 4 не может быть.
В таком случае формула для получения случайного числа в диапазоне [a, b] выглядит так:
rand() % длина_диапазона + сдвиг
где длина_диапазона вычисляется как b - a + 1, сдвиг является значением a.
В эту формулу также вписываются случаи, когда необходимо получить случайное число от 0 до N, т.е. они являются ее частными случаями.
Выведите на экран ряд случайных чисел, принадлежащих диапазону от 100 до 299 включительно.
С таким же успехом можно получать случайные отрицательные числа. Если диапазон задан как [-35, -1], то его длина будет равна -1 - (-35) + 1 = 35, что соответствует действительности; выражение получения случайного числа будет выглядеть так:
rand() % 35 - 35
Так, если остаток от деления составил 0, то мы получим -35, а если 34, то -1. Остальные остатки дадут значения в промежутке от -35 до -1.
Выведите на экран ряд случайных чисел, принадлежащих диапазону от -128 до 127 включительно.
Получение вещественных случайных чисел
Ситуация с вещественными числами выглядит несколько по-иному. Во-первых, мы не можем получить остаток от деления, если делимое или делитель дробные числа. Во вторых при вычислении длины диапазона нельзя прибавлять единицу.
Поясним вторую причину. Допустим диапазон задан как [2.50, 5.30]. Он состоит не из определенного количества чисел (как в случае целых), а из неопределенного (можно сказать, бесконечного) числа значений, т.к. вещественные числа можно представлять с различной степенью точности. Позже выполняя округление все равно будет шанс получить максимальную границу диапазона, поэтому для вычисления длины диапазона достаточно из максимума вычесть минимум.
Если разделить случайное число, преобразованное к вещественному типу, которое выдала функция rand()
, на значение константы RAND_MAX
, то получится вещественное случайное число от 0 до 1. Теперь, если это число умножить на длину диапазона, то получится число, лежащее в диапазоне от 0 до значения длины диапазона. Далее если прибавить к нему смещение к минимальной границе, то число благополучно впишется в требуемый диапазон. Таким образом формула для получения случайного вещественного числа выглядит так:
(float) rand() / RAND_MAX * (max - min) + min
Заполните массив случайными числами в диапазоне от 0.51 до 1.00. Выведите значение элементов массива на экран.
Равновероятные случайные числа
Функция rand()
генерирует любое случайное число от 0 до RAND_MAX
с равной долей вероятности. Другими словами, у числа 100 есть такой же шанс выпасть, как и у числа 25876.
Чтобы доказать это, напишем программу, подсчитывающую количество выпадений каждого из значений. Если выборка (количество "испытуемых") будет достаточно большой, а диапазон (разброс значений) маленьким, то мы должны увидеть, что процент выпадений того или иного значения приблизительно такой же как у других.
#include <stdio.h> #include <time.h> #define N 500 int main () { int i; int arr[5] = {0}; srand(time(NULL)); for (i=0; i < N; i++) switch (rand() % 5) { case 0: arr[0]++; break; case 1: arr[1]++; break; case 2: arr[2]++; break; case 3: arr[3]++; break; case 4: arr[4]++; break; } for (i=0; i < 5; i++) printf("%d - %.2f%%\n", i, ((float) arr[i] / N) * 100); }
В приведенной программе массив из пяти элементов сначала заполняется нулями. Случайные числа генерируются от 0 до 4 включительно. Если выпадает число 0, то увеличивается значение первого элемента массива, если число 1, то второго, и т.д. В конце на экран выводится процент выпадения каждого из чисел.
Чем больше значение константы N, тем меньше будут отличаться между собой проценты выпадения каждого из чисел.
Курс с решением задач:
pdf-версия