- 1. qrand
- 2. QRandomGenerator
- 3. Что делать?
- 4. Random.hpp
- 5. main.cpp
Генерация случайных чисел может понадобиться, например, для расчета урона от оружия в компьютерной игре или для представления графика из случайных чисел.
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); // Setting a base number for counting a random in 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); // Setting the base number for counting a random host in 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, в которой есть более приемлемый вариант генерации случайные числа. Реализация одной очень интересной библиотеки была найдена на Git Hub. На основе этой библиотеки я написал небольшой класс для генерации (читайте между строк, немного переписал для себя, чтобы разобраться).
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; // Random Number Generator std::mt19937 m_mt; // Standard random number generator 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 предоставляет возможности для достаточно качественной генерации псевдослучайных чисел.
А вот ссылка на библиотеку с GitHub , на которой я изучал этот вопрос.
А использование функции global() не решает ли эти проблемы?
Получаемая последовательность каждый раз новая.
Дмитрий, решает. Просто автор, видимо, не сильно озаботился изучением документации QRandomGenerator.
Да и в листинге с использованием qrand вызов функции qsrand на каждой итерации цикла наводит на мысль, что автор, мягко говоря, не очень понимает для чего это всё нужно.