Генерация случайных чисел в С++

В этой статье подробно поговорим о генерации случайных чисел (Примечание: правильнее будет сказать"псевдослучайные числа", т.к. ни один алгоритм не может выработать поистине случайное число, но для удобства я буду опускать слово "псевдо"). Довольно часто в программировании нужно получать случайные числа для выполнения каких-либо действий. Первое, что пришло для примера, это тасования карт в карточных играх или, например, игра в "кости", которая также требует генерации случайных чисел для моделирования бросания "костей". Примеров можно привести сколь угодно, особенно касательно компьютерных игр.

Для того, чтобы получить случайное число можно, конечно, написать и свою функцию, а можно использовать и стандартные библиотечные. Мы же с вами не будем "изобретать велосипед", и поэтому будем пользоваться только стандартными библиотечными функциями. Итак, для генерации случайных чисел в С++ используют функцию rand(), описанную в библиотеке stdlib.h. Как она работает, рассмотрим на простейшем примере.

//Генератор псевдослучайных чисел

#include <iostream>
#include <stdlib.h>

using namespace std;

int main()
{
    int m;

    m = rand();
    cout << m << endl;

    return 0;
}

Как видите, тут все просто: подключаем необходимую библиотеку, через директиву препроцессора #include и используем функцию rand() в программе. Попробуйте запустить программу и посмотреть выработанное ею число. А потом попробуйте запустить программу еще несколько раз. Наверное, вы заметили, что при каждом новом запуске функция rand() генерирует одно и то же число. Да, это действительно так! Но почему? Давайте разбираться вместе.

Технический момент. Т.к. rand() - это функция, то у нее есть свой прототип (описание), в котором указывается, что она может получать в качестве аргумента (аргументов) и что возвращает. Вот ее прототип (его можно найти и самостоятельно в файле stdlib.h, который обычно находится в папке include вашей среды программирования):

int rand(void);

Как видите, функция возвращает целые числа (от 0 до 32767), в качестве аргументов она не может ничего принимать, т.е. внутри скобок будет всегда пусто.

Продолжаем разбираться. Дабы понять, почему функция rand() всегда нам возвращает одно и то же число, заглянем внутрь ее. Вот, что мы там увидим

#define RAND_MAX 32767

unsigned long next = 1;

int rand(void)
{
    next = next * 1103515245m;
    return((unsigned int)(next / 65536)2768);
}

Не будем вдаваться в подробности кода. Скажу лишь то, что полученное случайное число с помощью этой функции, зависит от стартового числа next, которое, как вы видите, установлено в единицу. Отсюда и следует, что числа всегда получаются одинаковыми. Для того, чтобы избежать этой проблемы, в паре с rand() нужно использовать функцию srand(). Вот ее прототип

void srand(unsigned int);

А вот и ее внутренность

void srand(unsigned int seed)
{
    next = seed;
}

Как видите, эта функция изменяет стартовое число next, присваивая ему другую величину, получаемую функцией в качестве аргумента. В принципе, это число можно вводить каждый раз вручную, например, так

//Генератор псевдослучайных чисел

#include <iostream>
#include <stdlib.h>

using namespace std;

int main()
{
    int m, seed;

    cout << "Enter seed: ";
    cin >> seed;

    srand(seed);
    m = rand();
    cout << m << endl;

    return 0;
}

В этом случае, вводя разные значения переменной seed, мы будем получать различные случайные величины на выходе. Как вы понимаете, это не очень удобно. Чаще всего в качестве передаваемой величины в функцию srand() используют системное время в секундах. Согласитесь, что это наилучший способ, т.к. это число будет всегда разным, а соответственно, мы будем получать на выходе из rand() случайные числа.

Теперь нужно подумать, как передать в функцию srand() текущее системное время. А ответ прост: для этого есть библиотечная функция time(), описанная в библиотеке time.h. Вот ее прототип

time_t time(time_t*);

Не пугайтесь названию типа, т.к. его можно приравнять к обычному int. Для того, чтобы эта функция возвращала текущее время в секундах (секунды отсчитываются от 00:00:00), нужно вызывать ее с параметром NULL. Получится вот так

//Генератор псевдослучайных чисел

#include <iostream>
#include <stdlib.h>
#include <time.h>

using namespace std;

int main()
{
    int m;

    srand(time(NULL));
    m = rand();

    cout << m << endl;

    return 0;
}

Теперь рассмотрим вот такой вопрос. Допустим, что нам нужно генерировать число не на всем допустимом интервале, а только в каком-то определенном, к примеру, от 0 до 9 включительно. Как мы поступим в данном случае? У функции rand() есть свой синтаксис

m = a + rand() % b;

где a - это начальная точка, с которой начинается генерация,

b - это величина сдвига, определяющая интервал, на котором будет производиться генерация.

В нашем случае a = 0 (это число идет включительно), b = 10 (а это будет не включительно, т.к. это не конечная точка, а величина сдвига. Если от 0 отложить 10 чисел, то последним будет 9 включительно). Если бы нужно было генерировать числа в интервале, к примеру, от 30 до 50 включительно, то было бы так

m = 30 + rand() % 21;

//Генератор псевдослучайных чисел

#include <iostream>
#include <stdlib.h>
#include <time.h>

using namespace std;

int main()
{
    int m;
    srand(time(NULL));

    for(int i = 0; i < 10; i++)
    {
        m = 30 + rand() % 21;
        cout << m << endl;
    }

    return 0;
}

Поэкспериментируйте с программой выше, понаблюдайте за получаемыми величинами. Попробуйте сделать что-нибудь свое, чтобы лучше разобраться с генерацией случайных чисел в С++. Также могу посоветовать поэкспериментировать с добавлением в srand() функции clock(), возвращающей приблизительное процессорное время потраченное на работу с программой. Вот такая вот будет строка

srand(time(NULL) | clock());

В некоторых случаях возможно получение более случайных величин, хотя, лично я, принципиальной разницы не замечал.

Теперь еще один момент: получение отрицательных значений. Допустим, что нам нужно получать случайные величины в интервале от -100 до +100. Строка кода, отвечающая за рандомизацию, будет такой

m = rand() % 201 - 100;

И последний момент, это получение чисел с плавающей точкой, т.е. дробных. К примеру, нужно получить числа на интервале от 0.01 до 1. Смотрим код

//Генерация чисел с плавающей точкой

#include <iostream>
#include <stdlib.h>
#include <time.h>

using namespace std;

int main()
{
    double m;
    srand(time(NULL));

    for(int i = 0; i < 10; i++)
    {
        m = 0.01 * (rand() % 101);
        cout << m << endl;
    }

   return 0;
}

В завершении статьи напишу небольшую программу, которая запрашивает у пользователя интервал генерации случайных чисел, заполняет полученными величинами двумерный массив (матрицу) и выводит ее на экран

//Генерация чисел с плавающей точкой

#include <iostream>
#include <iomanip>
#include <stdlib.h>
#include <time.h>

void printMatrix(int[][10], const int);

using namespace std;

int main()
{
    const int SIZE = 10;
    int matrix[SIZE][SIZE], a, b;
    srand(time(NULL));

    //запрашиваем верхнюю и нижнюю границы рандомизации
    cout << "Enter a: ";
    cin >> a;
    cout << "Enter b: ";
    cin >> b;

    //проходим по ячейкам матрицы и заполняем
    for(int i = 0; i < SIZE; i++)
    {
        for(int j = 0; j < SIZE; j++)
        {
            matrix[i][j] = a + rand() % (b - a + 1);
        }
    }

    //вывод матрицы на печать
    printMatrix(matrix, SIZE);

    return 0;
}

//функция для печати массива
void printMatrix(int array[][10], const int S)
{
    cout << endl;

    for(int i = 0; i < S; i++)
    {
        for(int j = 0; j < S; j++)
        {
            cout << setw(5) << array[i][j];
        }

        cout << endl << endl;
    }
}

Здесь единственное замечание по коду, которое нужно объяснить подробнее - это строка

matrix[i][j] = a + rand() % (b - a + 1);

Т.к. пользователю удобнее вводить нижний и верхний индексы рандомизации, а не величину сдвига во втором случае, то мы производим некоторые математические вычисления. Допустим, что пользователь ввел 10 и 20, тогда в итоге получим такую строку кода

matrix[i][j] = 10 + rand() % 21;

что и требовалось.

Результат работы программы

Генерация случайных чисел в C++