Evgenii Legotckoi
Evgenii Legotckoi14 грудня 2017 р. 17:10

Qt/C++ - Урок 074. Генерація псевдовипадкових чисел, використання STD бібліотеки random

Генерація випадкових чисел може знадобитися, наприклад, для розрахунку шкоди від зброї в комп'ютерній грі або буде представляти графіка з випадкових чисел.

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 можна виділити досить якісну генерацію псевдовипадкових чисел.

А ось і посилання на бібліотеку з Гіт Хаба , по якій я вивчав це питання.

Рекомендуємо хостинг TIMEWEB
Рекомендуємо хостинг TIMEWEB
Стабільний хостинг, на якому розміщується соціальна мережа EVILEG. Для проектів на Django радимо VDS хостинг.

Вам це подобається? Поділіться в соціальних мережах!

Дмитрий
  • 29 жовтня 2020 р. 09:37
  • (відредаговано)

А использование функции global() не решает ли эти проблемы?

value = QRandomGenerator::global()->bounded(15, 43);

Получаемая последовательность каждый раз новая.

r
  • 17 січня 2021 р. 04:09

Дмитрий, решает. Просто автор, видимо, не сильно озаботился изучением документации QRandomGenerator.
Да и в листинге с использованием qrand вызов функции qsrand на каждой итерации цикла наводит на мысль, что автор, мягко говоря, не очень понимает для чего это всё нужно.

Коментарі

Only authorized users can post comments.
Please, Log in or Sign up
AD

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:50бали,
  • Рейтинг балів-4
m
  • molni99
  • 26 жовтня 2024 р. 01:37

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:80бали,
  • Рейтинг балів4
m
  • molni99
  • 26 жовтня 2024 р. 01:29

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:20бали,
  • Рейтинг балів-10
Останні коментарі
ИМ
Игорь Максимов22 листопада 2024 р. 11:51
Django - Підручник 017. Налаштуйте сторінку входу до Django Добрый вечер Евгений! Я сделал себе авторизацию аналогичную вашей, все работает, кроме возврата к предидущей странице. Редеректит всегда на главную, хотя в логах сервера вижу запросы на правильн…
Evgenii Legotckoi
Evgenii Legotckoi31 жовтня 2024 р. 14:37
Django - Урок 064. Як написати розширення для Python Markdown Добрый день. Да, можно. Либо через такие же плагины, либо с постобработкой через python библиотеку Beautiful Soup
A
ALO1ZE19 жовтня 2024 р. 08:19
Читалка файлів fb3 на Qt Creator Подскажите как это запустить? Я не шарю в программировании и кодинге. Скачал и установаил Qt, но куча ошибок выдается и не запустить. А очень надо fb3 переконвертировать в html
ИМ
Игорь Максимов05 жовтня 2024 р. 07:51
Django - Урок 064. Як написати розширення для Python Markdown Приветствую Евгений! У меня вопрос. Можно ли вставлять свои классы в разметку редактора markdown? Допустим имея стандартную разметку: <ul> <li></li> <li></l…
d
dblas505 липня 2024 р. 11:02
QML - Урок 016. База даних SQLite та робота з нею в QML Qt Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
Тепер обговоріть на форумі
Evgenii Legotckoi
Evgenii Legotckoi24 червня 2024 р. 15:11
добавить qlineseries в функции Я тут. Работы оень много. Отправил его в бан.
t
tonypeachey115 листопада 2024 р. 06:04
google domain [url=https://google.com/]domain[/url] domain [http://www.example.com link title]
NSProject
NSProject04 червня 2022 р. 03:49
Всё ещё разбираюсь с кешем. В следствии прочтения данной статьи. Я принял для себя решение сделать кеширование свойств менеджера модели LikeDislike. И так как установка evileg_core для меня не была возможна, ибо он писался…
9
9Anonim25 жовтня 2024 р. 09:10
Машина тьюринга // Начальное состояние 0 0, ,<,1 // Переход в состояние 1 при пустом символе 0,0,>,0 // Остаемся в состоянии 0, двигаясь вправо при встрече 0 0,1,>…

Слідкуйте за нами в соціальних мережах