Генерация случайных чисел может понадобиться, например, для расчёта урона от оружия в компьютерной игре или для представления графика из случайных чисел.
Qt предоставляет для генерации случайных числе функцию qrand , а также, начиная с Qt 5.10, класс QRandomGenerator.
Давайте разберём, как можно получить случайные значения в Qt, а также насколько они случайные.
qrand
Будем генерировать числа в диапазоне значений от и до. Для этого напишем две функции.
static int randomBetween(int low, int high) { return (qrand() % ((high + 1) - low) + low); } static int randomBetween(int low, int high, int seed) { qsrand(seed); // Установка базового числа для отсчёта рандома в qrand return (qrand() % ((high + 1) - low) + low); }
Первая функция просто генерирует случайное значение от наименьшего числа до наибольшего. Тогда как во второй с помощью функции qsrand устанавливается базовое число, которое служит основанием для генератора псевдослучайных числе Qt, от которого и генерируется число. Таким базовым числом может быть системное время в миллисекундах.
Применим данные функции.
#include <QCoreApplication> #include <QDateTime> #include <iostream> static int randomBetween(int low, int high) { return (qrand() % ((high + 1) - low) + low); } static int randomBetween(int low, int high, int seed) { qsrand(seed); // Установка базового числа для отсчёта рандома в qrand return (qrand() % ((high + 1) - low) + low); } int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); std::cout << "Random Qt 1 - "; for (int i = 0; i < 15; ++i) { std::cout << randomBetween(15, 43) << " "; } std::cout << std::endl; std::cout << "Random Qt 2 - "; for (int i = 0; i < 15; ++i) { std::cout << randomBetween(15, 43, QDateTime::currentMSecsSinceEpoch()) << " "; } std::cout << std::endl; return a.exec(); }
Получим следующий вывод.
Random Qt 1 - 15 21 31 27 17 34 43 33 22 32 30 34 35 38 31 Random Qt 2 - 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37
А теперь проанализируем полученный вывод.
В первом случае числа получились случайные... Но насколько? Если вы попробуете запустить программу несколько раз подряд, то увидите, что числа каждый раз будут одни и те же, что не очень хорошо и явно не очень случайно.
Что касается второго варианта, то здесь все числа получились одни и те же. Дело в том, что каждый раз мы пытались установить в качестве базового числа одни и то же число. Действительно, процессор с частотой в пару ГГц очень быстро выполнит тот простой цикл, а значит время в миллисекундах просто не успеет измениться, а значит и базовое число будет одинаковым. А при установке базового числа генерация будет производиться с самого начала и в данном случае вы увидите, что она не очень случайная.
А теперь посмотрим, что нам предложили в Qt 5.10
QRandomGenerator
Применение QRandomGenerator будет следующим
#include <QCoreApplication> #include <iostream> #include <QRandomGenerator> int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); std::cout << "Random Qt 3 - "; QRandomGenerator generator; for (int i = 0; i < 15; ++i) { qint64 value = generator.generate() & std::numeric_limits<qint64>::max(); std::cout << value << " "; } std::cout << std::endl; return a.exec(); }
Вывод будет следующим
Random Qt 3 - 853323747 2396352728 3025954838 2985633182 2815751046 340588426 3587208406 298087538 2912478009 3642122814 3202916223 799257577 1872145992 639469699 3201121432
По факту, здесь повторится та же проблема, что и у qrand , при многократном запуске программы вы увидите, что числа будут повторяться. В новости о выпуске Qt 5.10 сказано, что данный класс лучше функционирует, чем обычный qrand, но по факту проблема остаётся той же. Возможно при более длительном тестировании в рамках программы, которая постоянно использует генерацию случайных чисел можно увидеть существенные различия, но в рамках даже такого простого варианта уже заметны недостатки.
Что делать?
В попытках найти приемлемое решение для генерации случайных чисел для одного проекта, мне удалось найти информацию о том, что в составе STD Library для стандарта C++11 есть библиотека random, в которой есть более приемлемый вариант генерации случайных чисел. Вариант реализации одной очень интересной библиотеки удалось найти на Гит Хабе. На основе той библиотеки я написал небольшой класс для генерации (читайте между строк, немного переписал для себя, чтобы разобраться).
Random.hpp
Класс, который реализован здесь использует синглетон Майерса, чтобы сделать статические методы получения случайных значений в определённом диапазоне. Гораздо же проще вызывать в нужном месте один статический метод класса, чем каждый раз предварительно инициализировать все генераторы случайных чисел. Сам класс работает как с целочисленными типами, так и типами значений с плавающей запятой.
#ifndef RANDOM_HPP #define RANDOM_HPP #include <random> namespace details { /// True if type T is applicable by a std::uniform_int_distribution template<class T> struct is_uniform_int { static constexpr bool value = std::is_same<T, short>::value || std::is_same<T, int>::value || std::is_same<T, long>::value || std::is_same<T, long long>::value || std::is_same<T, unsigned short>::value || std::is_same<T, unsigned int>::value || std::is_same<T, unsigned long>::value || std::is_same<T, unsigned long long>::value; }; /// True if type T is applicable by a std::uniform_real_distribution template<class T> struct is_uniform_real { static constexpr bool value = std::is_same<T, float>::value || std::is_same<T, double>::value || std::is_same<T, long double>::value; }; } class Random { template <class T> using IntDist = std::uniform_int_distribution<T>; template <class T> using RealDist = std::uniform_real_distribution<T>; public: template <class T> static typename std::enable_if<details::is_uniform_int<T>::value, T>::type get(T from = std::numeric_limits<T>::min(), T to = std::numeric_limits<T>::max()) { if (from > to) std::swap(from, to); IntDist<T> dist{from, to}; return dist(instance().engine()); } template <class T> static typename std::enable_if<details::is_uniform_real<T>::value, T>::type get(T from = std::numeric_limits<T>::min(), T to = std::numeric_limits<T>::max()) { if (from > to) std::swap(from, to); RealDist<T> dist{from, to}; return dist(instance().engine()); } std::mt19937& engine() { return m_mt; } protected: static Random& instance() { static Random inst; return inst; } private: std::random_device m_rd; // Устройство генерации случайных чисел std::mt19937 m_mt; // Стандартный генератор случайных чисел Random() : m_mt(m_rd()) {} ~Random() {} Random(const Random&) = delete; Random& operator = (const Random&) = delete; }; #endif // RANDOM_HPP
main.cpp
А теперь применение
#include <QCoreApplication> #include "Random.hpp" int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); std::cout << "Random STD - "; for (int i = 0; i < 15; ++i) { std::cout << Random::get(15, 43) << " "; } std::cout << std::endl; return a.exec(); }
Вывод
Random STD - 38 29 36 38 21 32 33 39 31 15 33 16 36 38 35
В данном случае мы действительно получим случайный вывод чисел, а также числа не будут повторяться при каждом запуске программы. Что касается случайности, то я убедился в этом лично, когда для генерации уровня в игре применил данный класса Random. Каждый раз расстановка объектов в игре не повторялась. Чего очень сложно было достичь с использованием qrand .
Так что из ништяков C++11 можно выделить достаточно качественную генерацию псевдослучайных чисел.
А вот и ссылка на библиотеку с Гит Хаба , по которой я изучал данный вопрос.
А использование функции global() не решает ли эти проблемы?
Получаемая последовательность каждый раз новая.
Дмитрий, решает. Просто автор, видимо, не сильно озаботился изучением документации QRandomGenerator.
Да и в листинге с использованием qrand вызов функции qsrand на каждой итерации цикла наводит на мысль, что автор, мягко говоря, не очень понимает для чего это всё нужно.