Генератор случайных значений

Иногда в C++ у начинающих программистов возникает необходимость использовать различные случайные значения. Например, для проверки правильности работы написанной программы сортировки чисел. Проверка программ иногда требует ввода большого множества различных значений.
Генератор случайных значений для того и нужен, чтобы генерировать случайные значения, которые можно использовать. Простейший пример использования такого генератора — игра угадай число. Другой пример использования — создание паролей. Очень много возможностей есть, и оттого имеется определённая необходимость в генераторах случайных чисел.
До 2011г. язык C++ не имел полноценного генератора случайных значений. Генерация случайных чисел задача не из простых, хотя и может казаться простой. Существуют разные алгоритмы, многие математики бились над решением такой задачи. И долгое время генераторы создавали псевдослучайные числа. Генерируемые числа можно было вычислить. Для нас, новичков, это не критично, но в профессиональной среде это определённо было проблемой.
Первым делом я познакомлю вас с более ранним генератором чисел, т. е. с ненастоящим.

При каждом новом запуске такой программы на экран будет выводиться случайным образом либо 0, либо 1.
Немного разберём листинг #1.

  • srand(…) вызывается для того, чтобы при каждом новом запуске программы значения генерировались разными.

Новички часто забывают написать srand(time(0)), а потом ломают голову, отчего числа получаются одинаковыми.
srand(…) можно использовать и для генерации одних и тех же значений многократно.

Тут всё зависит от того, что отдаётся вовнутрь круглых скобок: отдаются разные значения, получаются разные цепочки чисел; отдаются одинаковые значения, получаются одинаковые цепочки чисел.

time(0) — это функция, отдающая системное время, представленное в секундах, прошедшее с 1 января 1970 года.

Поскольку при каждом новом запуске значения меняются, цепочки чисел получаются разными. Такой небольшой секрет работы srand(…)(time(0)); вы теперь знаете.
Простыми словами, генерация различных чисел в компиляторах С++ до 2011г. была жёстко привязана ко времени. Получаешь текущее время в секундах, подаёшь его в srand(…) и забираешь разные последовательности чисел из rand().
  • srand(…) называют функцией-семенем для rand().
Последовательность значений генерируется путём несложных арифметических операций, занимается этими манипуляциями rand(). Первое число для rand() берётся из srand(…), после чего происходит цепочка вычислений: второе число создаётся из первого, третье из второго,…, двадцатое из девятнадцтого. Таким образом настоящих случайных чисел нет, но поскольку секунды тикают, первое число последовательности может оказываться разным при каждом новом запуске программы, а это создаёт эффект случайности, которым мы, новички, и будем пользоваться в первых задачах в некоторых случаях.
Названия похожие, но просто запомните, что нужно в rand() отдать начальное значение, а отдать его можно с помощью srand(…). Если в srand(…) отдавать время, то от rand() будут разные цепочки чисел на выходе.
srand(…) требует вовнутрь своих круглых скобок аргумента, а rand() просит только пустых круглых скобок. Обратите на это своё внимание.

Я вам пытаюсь показать, что вся последовательность известна сразу. Если знать формулу расчёта, начальное значение, то всю последовательность можно расчитывать своими силами. Поэтому такой генератор называют ненастоящим генератором случайных чисел, псевдогенератором: ПГСЧ (псевдогенератор случайных чисел).
Чтобы каждый раз не ловить одну последовательность, можно указать на начало последовательности:
Последовательность: 1 2 3 4 5 6 7 8 9, в зависимости от начала её прочтения, может принимать разные формы:
— 5 6 7 8 9 1 2 3 4 5 6 7 —
— 8 9 1 2 3 4 5 6 7 —
— 2 3 4 5 6 7 8 9 1 2 —
Принцип вам должен стать ясен. Так вот, srand(…) и занимается тем, что указывает на начало последовательности, генерируемой rand().
Очень часто нужно генерировать числа, не выходящие из некоторого диапазона. Делается это следующим образом:

Диапазон задаётся обычной операцией взятия остатка от числа. Если вы понимаете, что такое остаток числа, как он получается при делении, то вам легко объяснить суть принципа.

Я не знаю, какое у вас первое число последовательности, поэтому не могу выбрать наиболее подходящие числа. Остаток берётся для определения диапазонов по очень простой причине: например, нужен диапазон от нуля до пяти, тогда проверяется остаток от деления на пять. Нужен от нуля до ста, проверяется остаток от деления на сто. Сама цепочка остатков не может содержать в себе чисел, превышающих остаток-делитель и равных остатку-делителю. 5%5 остаток ноль; 100%100 остаток ноль; 6%5 остаток один. В этом весь секрет укладки чисел в правильные диапазоны. Выбирается некоторое число из большой цепочки чисел, у него берётся остаток от деления и получается число, принадлежащее нужному диапазону, берущему отсчёт от нуля.
Если вы разобрались с описываемым мной алгоритмом формирования разных чисел, то можете и сами додумать, как сдвинуть диапазон от нуля. Ведь ноль не всегда нужная граница диапазона.

Для вывода на монитор будет использован цикл. Цикл поспособствует наглядности.

В листинге #7 сдвиг диапазона происходит на 15 позиций вправо. Т. е. было [0,9], а из-за сложения с 15 становится [15,24]. [0+15,9+15]
Одним из существенных недостатков показанного способа оказывается то, что числа ограничены константой RAND_MAX.

Генерировать числа, превышающие значение, хранимое в RAND_MAX, оказывается небольшой проблемой. Нельзя взять и просто сгенерировать такое число. Можете попробовать.
Пример одного из решений приведён в листинге #8

В этом примере случайное выбранное число сохраняется в переменную i. Тип переменной задан long long как тип, умеющий хранить наибольший диапазон целых чисел (это фундаментальный тип, он есть во всех компиляторах С++). Но, конечно, совсем гигантские числа этот тип хранить не способен, ёмкость его ограничена какими-то своими пределами.
Подводим итоги:
  • Сначала нужно инициализировать rand(). Говорят, что нужно засеять rand() семенем. Делается это с помощью srand(…).
  • Начало последовательности чисел определяется значением, уходящим в srand(…). Если в srand(…) уходят разные значения, последовательность чисел получается разной, при неизменном значении последовательность будет одинаковой.
  • Динамически изменяющиеся значения подбираются из системного времени, они как раз и используются для случайности значений на каждый новый запуск программы: srand(time(0));
  • Диапазон выпадающих значений определяется с помощью операции взятия остатка: %.
  • Сдвиг диапазонов выпадающих значений определяется сложением или вычитанием + или .
  • Свойствами настоящей уникализации rand() не обладает.
  • rand() ограничивается значением RAND_MAX.

12 комментариев на «“Генератор случайных значений”»

  1. Anonymous:

    #include #include using namespace std;int main(){ srand(time(0)); cout<<rand()%2; return 0;}в визуал с++ выдает такую ошибку (всего 1)error C3861: time: идентификатор не найденчто с этим делать? что нужно, чтобы обозначить time?

    Автор сайта отвечает:

    (инклуды тут обрезаются)

  2. Some intu Coder:

    ну, я когда сомневался что инициализация сбрасывалась, делал так:

    Автор сайта отвечает:
    чтобы код отобразился верно, нужно его написать между тегами
    [php]Исходный код[/php]

     
    Объявлять переменную внутри цикла, это на кощунство похоже. Само объявление переменной лучше делать до цикла, а операции с уже объявленной ранее переменной производить внутри цикла.

    Но об оптимальности кажется сейчас не говорили. Думаю, кому надо, поймет что в форе может быть что — нить для их задачи. Я и цикл то воткнул исключительно для демонстрации, что рандом переинициализировался. согласен, выделять память под одну переменную внутри цикла в данном случае неразумно. Ладно, коль обучать нормальному коду, переделаю.

  3. Vitaly Streltsov:

    В вашем примере «А вот так делал я. Только положительные шестизначные числа» числа не настолько рандомны, как нужно — у всех на конце по два нуля от умножения в коде на 100. Код, например, генерировать , пока рандомное число не станет ХХХХ10, будет генерировать бесконечно 😉 Может быть, ваше число «double а» умножать не на 100, а на рандомное b, которое b>=100. ?

    Автор сайта отвечает:
    Можно просто генерировать еще одно число в диапазоне 0-99 и прибавлять это число к моему и следить, чтоб число не превысило нужное
    Это моя ошибка, но как-то внимания не обратил. Давно очень описывал эту тему.

  4. Аноним:

    как заполнить массив рандомными символами?

  5. Vladimir:

    Использую библиотеку <time.h>, и все что написанно тут перепробовал, но вот что меня смущает, рандомностью тут чето и не пахнет, если запускать (srand(time(o));  cout << rand()%»любое число»;) много раз подряд, видно что число которое каждый раз выдается немного больше предыдущего, и так пока не достигнет предела а потом начинает снова с минимального значения по возрастающей, а где рандомность?
    В Delphi функция randomize, как я над ней не издевался, действительно дает рандомное число из диапазона значений.

    Автор сайта отвечает:
    Это генератор псевдослучайных чисел. Слово “псевдо” обозначает, что они случайными не являются, так уж устроена функция rand(), она зависит от времени. Настоящие генераторы случайных чисел появляются с С++11, но даже там имеется зависимость от оборудования. #include <random>

    Но то, что Вы написали, где написали, что число слегка нарастает и потом всё сначала — это чушь.
    Вот небольшой (и неэффективный) пример, который генерирует убывающие последовательности. Будет работать с компиляторов, поддерживающих С++11

    Здесь просто показано, что Ваше изречение на самом деле чушь, и rand() генерирует разные числа, вплоть до обратносказанного.

  6. Vladimir:

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

    Автор сайта отвечает:
    Не пригодится, есть готовые решения. Ваш генератор такой же генератор случайных чисел каким он и был, когда было просто rand(), только Вы усложнили всё.

  7. Vladimir:

    Тут в голову идея пришла, оцените если несложно, суть следующая:
    Источником энтропии(рандома) могут послужить числа с плавающей точкой(вещественные), ведь при любых математических операциях над данными типами у них есть предел точности(действительное кол-во знаков после запятой), превышая который точность теряется и получаются совершенно непредсказуемые значения, а что если использовать их как рандом?

    Автор сайта отвечает:
    Я не понял вопрос. Я не понял зачем изобретать свой random.

  8. Vladimir:

    Ну изобретать что то свое это только +(пускай что то похожее уже есть), развивает мышление и находчивость. А числовой диапазон… рассмотрим тип long double, он имеет 38 значений после запятой(если мне не изменяет память) из них только около 12 предел точности(если мне не изменяет память), соответственно в нашем распоряжении 26 значное число, сомневаюсь что кому то нужен рандом превышающий данный диапазон. Отсюда следует что можно указывать свой диапазон в рамках данного диапазона. И вуаля, мы получим самый рандомный рандом а не все те псевдорандомы что нам прелогают.

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

    Начиная с С++11 идёт не генератор псевдослучайных чисел чисел, хотя и становится генератором псевдослучайных при определённом стечении обстоятельств (каких именно сказать не могу).

    Когда вы используете функцию rand() из stdlib.h для настоящей случайности, вы уже используете ненастоящую случайность. Но только думается мне, что сейчас Вы не на той стадии, чтобы различить настоящую и ненастоящую, так как Вы придрались к rand() на абсолютно пустом месте и не по существу.

    Справедливости ради отмечу, что предлагаемый Вами алгоритм недалеко от алгоритма, используемого функцией rand(), только вместо не изменяющегося числа она использует системное время.

  9. Алексей:

    Спасибо отличная статья, единственный сайт в инете где я нормально и доходчиво узнал почему у меня не работал рандом!

  10. MegaKillBot:

    Почему директива <iostream.h>, если правильно <iostream> (без «.h»)?
    Я аж психовать стал, пока при помощи чуда, не обратил на объявления библиотек внимания.

  11. Александр:

    😈 Vladimir говорит:
    09.05.2016 в 3:36 пп
    Ну изобретать что то свое это только +(пускай что то похожее уже есть), развивает мышление и находчивость. А числовой диапазон… рассмотрим тип long double, он имеет 38 значений после запятой(если мне не изменяет память) из них только около 12 предел точности(если мне не изменяет память), соответственно в нашем распоряжении 26 значное число, сомневаюсь что кому то нужен рандом превышающий данный диапазон. Отсюда следует что можно указывать свой диапазон в рамках данного диапазона. И вуаля, мы получим самый рандомный рандом а не все те псевдорандомы что нам прелогают.

    Возможно Владимир хочет сказать, что предсказать число за пределом точности невозможно?

  12. Daniil:

    А так вообще можно?

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Поиск

 
     

Случайная книга в электронном формате

https://www.litres.ru/dhananya-gadre/zanimatelnye-proekty-na-baze-mikrokontrollerov-tinyavr-7012802/?lfrom=15589587
Яндекс.Метрика