Случайные числа в C++
#include <iostream>
#include <random>
using namespace std;
int main() {
random_device rd;
mt19937 gen(rd());
uniform_int_distribution<int> dice(1, 6);
for (int i = 0; i < 10; i++) {
cout << dice(gen) << " ";
}
cout << "\n";
return 0;
}Программа выводит 10 случайных чисел от 1 до 6 включительно, и при каждом запуске — разные.
Из чего это собрано (всё из <random>):
mt19937— генератор: выдаёт длинную последовательность «случайных» чисел по алгоритму «вихрь Мерсенна». Создаётся один раз на всю программу.random_device rd;иgen(rd())— зерно (seed), с которого генератор начинает. Разное зерно — разная последовательность.random_deviceберёт настоящую случайность от системы.uniform_int_distribution<int> dice(1, 6)— распределение: превращает числа генератора в равновероятные целые от 1 до 6, обе границы включены.
Дробные числа и перемешивание
#include <iostream>
#include <random>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
mt19937 gen(random_device{}());
uniform_real_distribution<double> unit(0.0, 1.0);
cout << unit(gen) << "\n"; // от 0 до 1, единица не входит
vector<int> cards = {1, 2, 3, 4, 5};
shuffle(cards.begin(), cards.end(), gen);
for (int c : cards) {
cout << c << " ";
}
cout << "\n";
return 0;
}uniform_real_distribution<double>(a, b) даёт дробные числа от a до b, причём b не входит. shuffle перемешивает вектор в случайном порядке.
Одинаковые числа при каждом запуске
Иногда это и нужно: при отладке удобно, чтобы «случайные» данные повторялись. Задайте зерно числом: mt19937 gen(12345); — последовательность будет одной и той же при каждом запуске. Но разные компиляторы могут по-разному переводить её в числа диапазона, поэтому в g++ и в Visual Studio результаты uniform_int_distribution с одним зерном могут отличаться.
В старых версиях MinGW для Windows random_device выдавал одно и то же при каждом запуске. Если числа не меняются, возьмите зерно от часов: mt19937 gen(chrono::steady_clock::now().time_since_epoch().count()); (нужен #include <chrono>).
А rand()?
В старом коде и учебниках встречается rand() из <cstdlib>:
#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;
int main() {
srand(time(0));
int x = rand() % 100 + 1; // от 1 до 100
cout << x << "\n";
return 0;
}Для простой игры этого хватит, но у rand есть недостатки:
- наибольшее значение
RAND_MAXможет быть всего 32767 — так в Visual Studio. Тогдаrand() % 1000000никогда не даст числа больше 32767; - остаток
% nделает одни числа чуть вероятнее других; srand(time(0))меняет зерно раз в секунду: две программы, запущенные в одну секунду, получат одинаковые числа.
Частые ошибки
- Генератор создаётся в цикле или в функции при каждом вызове с одним и тем же зерном — числа повторяются. Создайте генератор один раз (например, глобально или в
main) и передавайте его по ссылке:mt19937& gen. srandвызывается перед каждымrand— та же ошибка: зерно надо задать один раз в начале программы.- Границы:
uniform_int_distribution(1, 6)включает и 1, и 6, аrand() % 6даёт числа от 0 до 5.