В разгар создания STL и бурной войны за стандарт языка C++ ряд программистов разработали собственную кроссплатформенную библиотеку классов, обеспечивающих разработчиков инструментами для решения повседневных задач, таких как обработка данных, алгоритмы, работа с файлами и т. д. Эта библиотека называется Boost. Проект настолько успешен, что возможности Boost заимствуются и вписываются в стандарт языка, начиная с C++11. Одним из таких дополнений является усовершенствованная работа со случайными числами.

Функции rand() и srand() относятся к школьному уровню и пригодны для написания простых программ. Минусом этих функций является генерация недостаточно хорошей последовательности псевдослучайных чисел (картинка выше). Также возможностей простых функций не хватает при разработке сложных проектов.

Для решения возникшей задачи были придуманы генераторы случайных чисел (далее ГСЧ). С их появлением значительно улучшилась работа по генерации многих типов данных как псевдо-, так и истинно случайных. Примером генерации истинно случайных чисел является шум на картинке ниже.

Генератор псевдослучайных чисел

Традиционный алгоритм создания СЧ совмещал в себе одновременно алгоритм создания непредсказуемых битов и превращение их в последовательность чисел. В библиотеке random C++, являющаяся частью Boost, разделили эти два механизма. Теперь генерация случайных чисел и создание из них распределения (последовательности) происходит отдельно. Использование распределения является совершенно логичным. Потому что случайное число без определенного контекста не имеет смысла и его сложно использовать. Напишем простую функцию, которая бросает кость:

Типичной ошибкой тех, кто изучает random, является игнорирование создания распределения и переход сразу к созданию случайных чисел способом, к которому они привыкли. Например, рассмотрим вышеописанную функцию.

Некоторые считают такое ее использование допустимым. Ведь C++ позволяет так работать. Однако создателями библиотеки Boost и стандартов C++11 строго рекомендуется так не делать. В лучшем случае это окажется просто плохой на вид код, а в худшем – это будет работающий код, совершающий ошибки, которые очень сложно поймать. Использование распределений гарантирует, что программист получит то, что ожидает.

Инициализация генератора и seed

Этап объявления, определения и создания сущностей зачастую рассматривается как нечто, не стоящее особого внимания. Но недостаточно вдумчивая инициализация генератора случайных чисел может сказаться на его надлежащей работе.

Первые 2 инициализации эквивалентны. И по большей части имеют отношение к вкусу или к стандартам написания красивого кода. А вот следующая инициализация в корне отличается.

«31255» — это называется seed (семя, первоисточник) — число, на основе которого генератор создает случайные числа. Ключевым моментом здесь является то, что при такой инициализации тип seed должен быть таким же или приводимым к типу, с которым работает генератор. Этот тип доступен через конструкцию decltype(e()), или result_of, или typename.

Почему генератор создает одинаковые последовательности?

Когда программа запускается несколько раз, генератор всегда создает одну и ту же последовательность чисел, если его инициализация не меняется, то есть определение генератора происходит одинаковым образом от запуска к запуску программы. С одной стороны, такое самовоспроизведение чисел генератором полезно, например, при отладке. А с другой – является нежелательным и может создавать проблемы.

Соответственно, чтобы избежать повторения последовательности чисел, генератор должен инициализироваться разными значениями при каждом запуске программы. Как раз для этих целей можно использовать seed. Стандартным способом инициализации ГПСЧ является передача ему в качестве seed значения time(0) из заголовочного файла ctime. То есть генератор будет инициализироваться значением, равным количеству секунд, прошедших с момента 1 января 00 часов 00 минут 00 секунд, 1970 года по UTC.

Инициализация ГПСЧ другим генератором

Инициализации временем может быть недостаточно для решения ряда задач. Тогда можно определить ГПСЧ через другой генератор. Здесь хотелось бы сделать отступление и поговорить об одном мощном инструменте, позволяющем создавать по-настоящему случайные числа.

Random_device – генератор истинно случайных чисел

Все генераторы псевдослучайных чисел являются детерминированными. То есть имеют определение. Или другими словами, получение случайных чисел основано на математических алгоритмах. Random_device же является недетерминированным. Он создает числа на основе стохастических (случайных с др.-греч.) процессов. Такими процессами могут быть изменения фазы или амплитуды колебаний тока, колебания молекулярных решеток, движения воздушных масс в атмосфере и т.д.

Очевидно, что не у каждого компьютера и не в каждой системе может быть встроена возможность получить случайное число на основе стохастического процесса. Поэтому прибегать к использованию random_device стоит только в случае необходимости. Его работа может отличаться от системы к системе, от компьютера к компьютеру, а может и вовсе быть недоступной. Поэтому при использовании генератора истинно случайных чисел нужно обязательно предусмотреть обработку ошибок.

Использование random_device в качестве seed для ГПСЧ

Ничего принципиально нового в этом коде нет. При этом с каждым запуском ГПСЧ инициализируется случайными значениями, которые создает генератор истинно случайных чисел rd.

Стоит также отметить, что значение инициализации генератора может быть сброшено в любой момент:

Обобщим: генераторы и распределения

Генератор (engine) – это объект, который позволяет создавать разные равновероятные числа.

Распределение (distirbution) – это объект, который преобразует последовательность чисел, созданных генератором, в распределения по определенному закону, например:

  • равномерное (uniform);
  • нормальное — гауссово (normal);
  • биномиальное (binomial) и т. д.

Рассмотрим генераторы стандартной библиотеки C++.

  1. Новичкам достаточно использовать default_random_engine, оставив выбор генератора библиотеке. Выбран будет генератор на основе сочетания таких факторов, как производительность, размер, качество случайности.
  2. Для опытных пользователей библиотека предоставляет 9 заранее настроенных генераторов. Они сильно отличаются друг от друга производительностью и размерами, но в то же время их качество работы было подвержено серьезным тестам. Часто используется генератор под названием Mersenne twister engines и его экземпляры mt19937 (создание 32-битных чисел) и mt19937_64 (создание 64-битных чисел). Генератор представляет собой оптимальное сочетание скорости работы и степени случайности. Для большинства возникающих задач его будет достаточно.
  3. Для экспертов библиотека предоставляет собой конфигурируемые шаблоны генераторов, позволяющие создавать дополнительные виды генераторов.

Рассмотрим ключевые аспекты распределений. В стандарте языка их насчитывается 20 штук. В примере выше использовалось равномерное распределение библиотеки random C++ в диапазоне [a, b] для целых чисел — uniform_int_distribution. Такое же распределение можно использовать для действительных чисел: uniform_real_distribution с такими же параметрами a и b промежутка генерации чисел. При этом границы промежутка включены, то есть [a, b]. Перечислять все 20 распределений и повторять документацию C++ в статье смысла не имеет.

Следует отметить, что каждому распределению соответствует свой набор параметров. Для равномерного распределения это промежуток от a до b. А для геометрического (geometric_distribution) параметром является вероятность успеха p.

Большая часть распределений определена как шаблон класса, для которого параметром служит тип значений последовательности. Однако некоторые распределения создают последовательности только значения int или только значения real. Или, например, последовательность Бернулли (bernoulli_distribution) предоставляющая значения типа bool. Так же как и с ГСЧ, пользователь библиотеки может создавать собственные распределения и использовать с встроенными генераторами или с генераторами, которые создаст.

На этом возможности библиотеки не ограничиваются. Они значительно шире. Но предоставленной информации достаточно для использования и базового понимания генератора случайных чисел в C++.

Краткая справка: Random в стиле .Net

В .Net framework также присутствует класс Random для создания псевдослучайных чисел. Рассмотрим пример генерации Random number С++/CLI.

Для тех, кто работает в Visual Studio и никак не может понять, почему пространство имен System не определено.

Чтобы работать с .net необходимо подключение CLR. Делается это двумя способами.1) Создание проекта не windows console app, а с поддержкой CLR — Console application CLR (Консольное приложение CLR).2) Подключить поддержку CLR в настройках уже созданного проекта: свойства проекта(вкладка "проект", а не "сервис") -> конфигурация -> общее -> значения по умолчанию -> в выпадающем списке пункта "поддержка общеязыковой среды выполнения(CLR)" выбрать "Поддержка CLR-среды (/clr)".

В данном случае вся работа происходит благодаря функции Random Next C++/CLI.

Стоит особо отметить, что .net является большой библиотекой с обширными возможностями и использует свою версию языка, называемую C++/CLI от Common Language Infrastructure. В общем, это расширение C++ под платформу .Net.

Рассмотрим в конце несколько примеров, чтобы лучше понять работу со случайными числами.

Заключение

Любые технологии и методы постоянно развиваются и совершенствуются. Так случилось и с механизмом генерации случайных чисел rand(), который устарел и перестал удовлетворять современным требованиям. В STL существует библиотека random, в .Net Framework — класс Random для работы со случайными числами. От использования rand следует отказываться в пользу новых методов, т. к. они соответствуют современным парадигмам программирования, а старые методы будут выводиться из стандарта.

Недавно я пробовал программу для генерации случайных чисел в C ++ с использованием random engines определяется в #include , Моя программа выглядит следующим образом:

Просто, определенно! Но когда я попробовал вывод, числа были менее случайными, как эти: —

Когда я немного изменил свою программу & объявил default_random_engine как static или сделал это global тогда мой вывод был правильным, как этот:

Кто-нибудь может указать, что на самом деле идет не так в моей программе изначально & Как небольшие изменения помогли мне получить лучший результат?

Решение

Каждый раз, когда вы бежите random вы повторно заполняете генератор одинаковыми или очень близкими к одному и тому же начальному числу, поэтому вы получаете одинаковые выходные данные на каждой итерации или, если он все-таки изменится, его будет не много.

Чтобы это исправить, нужно только один раз запустить генератор, а затем продолжать его вызывать. Делая его статичным, он работает, как и ожидалось, так как генератор создается и засевается только после того, как вы продолжите идти по случайной последовательности вместо получения первого случайного числа, которое он создаст.

Обычно генераторы случайных чисел имеют внутреннее значение, которое они задают с помощью алгоритма, и что алгоритм выделяет, это то, что они возвращают для случайного числа. Это число затем сохраняется для следующей итерации алгоритма. Вот как мы получаем случайную последовательность.

Если мы используем то же самое начальное число, мы получим ту же самую последовательность вывода, поскольку мы начинаем с того же числа. В вашем случае ваше семя будет меняться только каждый раз chrono::steady_clock::now() продвигается, и ваш цикл работает быстрее, поэтому вы получаете одинаковое время (начальное значение) для каждого вызова.

Другие решения

Что важно знать о генераторах псевдослучайных чисел, так это то, что они генерируют последовательность чисел, а «случайность» — это качество, которое последовательность проверено на. То есть нельзя сказать, что конкретное число случайно или нет (‘4’ — это случайное число? ). Вы вместо этого говорите, является ли последовательность чисел случайной или нет.

Когда вы «затравляете» pRNG, вы обычно выбираете одну из последовательностей, которые он может сгенерировать, и эти числа являются случайными по отношению к остальной части последовательности. Таким образом, в общем, если вы хотите «случайность», вы хотите сначала выбрать конкретную последовательность, а затем использовать последовательные числа из этой конкретной случайной последовательности.

В вашем коде без static Вы выбираете последовательность на каждой итерации цикла, а затем используете одно число из этой последовательности. Поскольку вы не используете много значений из одной и той же последовательности, неудивительно, что результат не выглядит случайным.

Когда вы добавляете static двигатель больше не высевается на каждой итерации цикла. Вместо этого он засевается один раз, и каждая итерация цикла использует последовательные значения из этой последовательности, как вы и должны делать. И это то, что static означает для переменных области видимости блока: это означает, что переменная живет вне блока, в котором она объявлена, и что она будет инициализирована только в первый раз.

static действительно решает вашу проблему, однако я бы сказал, что это не хорошее решение. static переменные имеют некоторое сходство с глобальными переменными и вызывают некоторые из тех же проблем. Вместо этого я бы порекомендовал вам явно переместить двигатель за пределы цикла. Например:

Теперь государство явно управляется и передается в random() функция, которая лучше, чем полагаться на скрытое и неявное поведение области видимости блока static переменная.

Кроме того, в целом, я бы не рекомендовал использовать время в качестве единственного источника исходных данных. Часы с низким разрешением могут не обеспечивать новые семена достаточно часто, и программа, запускаемая в разных местах одновременно, может использовать одно и то же семя.

Моя рекомендация для посева:

(Новичкам не нужно знать, что такое mt19937 или любой другой бит. Им просто нужно знать, чтобы вставить это в соответствующем месте и как использовать eng , В вашем коде вам нужно заменить каждое использование default_random_engine с mt19937 .)

I want to be able to generate random values between 0.0 and 1.0

I’ve tried to use

Generating random value in a loop gives me always these values:

What can I do to really get random values? Doesn’t seem that random if I always get the same ones.

3 Answers 3

if open multiply programs which use the same random number generator
they will output the same results, because they have the same value of seed which is time.
This issue solved by using random device, see description beyond:*/

If you are referring to the fact that you get the same results for each execution of the program, that’s because you need to provide a seed based on some naturally random value (e.g. some number input by the user, or the number of milliseconds elapsed since the computer was turned on, or since January 1, 1970, etc.):

I have found another good solution.

Not the answer you’re looking for? Browse other questions tagged c++ c++11 std or ask your own question.

Linked

Related

Hot Network Questions

To subscribe to this RSS feed, copy and paste this URL into your RSS reader.

site design / logo © 2020 Stack Exchange Inc; user contributions licensed under cc by-sa 4.0 with attribution required. rev 2020.1.10.35756