- 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); // Установка базового числа для відліку рандома в 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 на каждой итерации цикла наводит на мысль, что автор, мягко говоря, не очень понимает для чего это всё нужно.