Как сгенерировать случайное число на C ++?


161

Я пытаюсь сделать игру с кубиками, и мне нужно, чтобы в ней были случайные числа (чтобы смоделировать стороны кубика. Я знаю, как сделать это между 1 и 6). С помощью

#include <cstdlib> 
#include <ctime> 
#include <iostream>

using namespace std;

int main() 
{ 
    srand((unsigned)time(0)); 
    int i;
    i = (rand()%6)+1; 
    cout << i << "\n"; 
}

не работает очень хорошо, потому что, когда я запускаю программу несколько раз, вот результат, который я получаю:

6
1
1
1
1
1
2
2
2
2
5
2

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


59
Помимо вопросов распределения, имейте в виду, что случайные числа дают возможность получить один и тот же результат несколько раз подряд. Если бы вы гарантированно не получили одно и то же число дважды подряд, результаты не были бы случайными, не так ли?
cdhowie

5
Почему вы думаете, что эти числа не случайны? Бросьте кубик по-настоящему, и вы вполне можете добиться такого результата. Если бы они гарантированно были разными между каждым броском, тогда это было бы не случайно.
mattjgalloway

3
Также прочтите eternalconfuzzled.com/arts/jsw_art_rand.aspx, почему использование оператора модуля не всегда является хорошей идеей.
Benjamin Bannier

4
Вы неправильно понимаете гораздо больше, чем можно вместить в комментарий или даже ответ. Вам необходимо самостоятельно узнать о генераторах псевдослучайных чисел, о начальных числах, о важности выбора действительно случайных начальных чисел и о равномерном распределении.
Kerrek SB

21
Когда вы засеиваете со временем. Это также означает, что если вы запускаете свою программу более одного раза в секунду, вы получите то же число.
Мартин Йорк

Ответы:


88

Самая фундаментальная проблема вашего тестового приложения заключается в том, что вы вызываете srandодин раз, затем randодин раз вызываете и выходите.

Весь смысл srandфункции состоит в том, чтобы инициализировать последовательность псевдослучайных чисел случайным начальным числом .

Это означает , что если вы передаете то же значение для srandдвух различных приложений (с той же srand/ randреализацией) , то вы получите точно такую же последовательность из rand()считанных значений после этого в обоих приложениях.

Однако в вашем примере приложения псевдослучайная последовательность состоит только из одного элемента - первого элемента псевдослучайной последовательности, сгенерированной из начального числа, равного текущему времени secondточности. Что вы ожидаете увидеть на выходе?

Очевидно, что когда вы запускаете приложение в одну и ту же секунду - вы используете одно и то же начальное значение - таким образом, ваш результат, конечно же, тот же (как уже упоминал Мартин Йорк в комментарии к вопросу).

На самом деле вы должны звонить srand(seed)один раз, а затем звонить rand() много раз и анализировать эту последовательность - она ​​должна выглядеть случайной.

РЕДАКТИРОВАТЬ:

О, я понял. Видимо словесного описания недостаточно (может языковой барьер что ли ... :)).

ОК. Пример старомодного кода C, основанный на тех же srand()/rand()/time()функциях, которые использовались в вопросе:

#include <stdlib.h>
#include <time.h>
#include <stdio.h>

int main(void)
{
    unsigned long j;
    srand( (unsigned)time(NULL) );

    for( j = 0; j < 100500; ++j )
    {
        int n;

        /* skip rand() readings that would make n%6 non-uniformly distributed
          (assuming rand() itself is uniformly distributed from 0 to RAND_MAX) */
        while( ( n = rand() ) > RAND_MAX - (RAND_MAX-5)%6 )
        { /* bad value retrieved so get next one */ }

        printf( "%d,\t%d\n", n, n % 6 + 1 );
    }

    return 0;
}

^^^ ЭТА последовательность из одного запуска программы должна выглядеть случайной.

ОБРАТИТЕ ВНИМАНИЕ, что я не рекомендую использовать rand/ srandfunctions в производстве по причинам, объясненным ниже, и я абсолютно не рекомендую использовать функцию timeв качестве случайного начального числа по причинам, которые IMO уже должны быть довольно очевидными. Они хороши для образовательных целей и иногда для иллюстрации сути, но для любого серьезного использования они в основном бесполезны.

РЕДАКТИРОВАТЬ2:

При использовании стандартной библиотеки C или C ++ важно понимать, что на данный момент нет ни одной стандартной функции или класса, производящего действительно случайные данные окончательно (гарантированные стандартом). Единственный стандартный инструмент, который подходит к этой проблеме, - это std :: random_device, который, к сожалению, до сих пор не дает гарантий фактической случайности.

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

Однако случайные числа уровня безопасности - это отдельная отрасль, заслуживающая отдельной статьи.

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

Исходный вопрос и повторяющееся множество идентичных / похожих вопросов (и даже множество ошибочных «ответов» на них) указывают на то, что в первую очередь важно отличать случайные числа от псевдослучайных чисел И понимать, что такое последовательность псевдослучайных чисел в во-первых, И понять, что генераторы псевдослучайных чисел НЕ используются так же, как вы могли бы использовать истинные генераторы случайных чисел.

Интуитивно, когда вы запрашиваете случайное число - возвращаемый результат не должен зависеть от ранее возвращенных значений и не должен зависеть от того, запрашивал ли кто-либо что-либо раньше, и не должен зависеть в какой момент и с помощью какого процесса, на каком компьютере и от какого генератора и в какая галактика запрашивалась. Это то, что в конце концов означает слово «случайный» - непредсказуемость и независимость от чего-либо - иначе это уже не случайность, верно? С такой интуицией вполне естественно искать в сети какие-то магические заклинания, которые можно использовать, чтобы получить такое случайное число в любом возможном контексте.

^^^ ТАКОЕ интуитивное ожидание ОЧЕНЬ НЕПРАВИЛЬНО и вредно во всех случаях, связанных с генераторами псевдослучайных чисел, несмотря на то, что они разумны для истинных случайных чисел.

Хотя осмысленное понятие «случайное число» существует (своего рода) - такого понятия, как «псевдослучайное число» не существует. Псевдо-генератор случайных чисел на самом деле производит псевдослучайных чисел последовательности .

Псевдослучайная последовательность на самом деле всегда детерминирована (предопределена своим алгоритмом и исходными параметрами), т.е. в ней фактически нет ничего случайного.

Когда эксперты говорят о качестве ГПСЧ, они на самом деле говорят о статистических свойствах сгенерированной последовательности (и ее примечательных подпоследовательностей). Например, если вы объединяете два высококачественных PRNG, используя их оба по очереди - вы можете получить плохую результирующую последовательность - несмотря на то, что они генерируют хорошие последовательности каждая по отдельности (эти две хорошие последовательности могут просто коррелировать друг с другом и, таким образом, плохо сочетаться).

Конкретно rand()/ srand(s)пара функций обеспечивают особую для каждого процесса , не поточно-(!) Псевдослучайных чисел последовательности генерируются с помощью алгоритма реализации. Функция rand()производит значения в диапазоне [0, RAND_MAX].

Цитата из стандарта C11 (ISO / IEC 9899: 2011):

srandФункция использует аргумент в качестве затравки для новой последовательности псевдослучайных чисел , которые будут возвращены при последующих вызовах rand. Если srandзатем вызывается с тем же начальным значением, последовательность псевдослучайных чисел должна быть повторена. Если randвызывается до выполнения каких-либо вызовов srand, должна быть сгенерирована та же последовательность, что и при srandпервом вызове с начальным значением 1.

Многие люди разумно ожидают, что rand()это даст последовательность полунезависимых равномерно распределенных чисел в диапазоне 0до RAND_MAX. Ну, безусловно, должен (в противном случае это бесполезно), но, к сожалению, этого не требует не только стандарт - есть даже явный отказ от ответственности, в котором говорится, что «нет никаких гарантий относительно качества созданной случайной последовательности» . В некоторых исторических случаях rand/ srandреализация была действительно очень плохого качества. Хотя в современных реализациях это, скорее всего, достаточно хорошо, но доверие нарушено, и его нелегко восстановить. Помимо того, что его небезопасный характер делает его безопасное использование в многопоточных приложениях сложным и ограниченным (все еще возможно - вы можете просто использовать их из одного выделенного потока).

Новый шаблон класса std :: mersenne_twister_engine <> (и его удобные typedefs - std::mt19937/ std::mt19937_64с хорошей комбинацией параметров шаблона) предоставляет генератор псевдослучайных чисел для каждого объекта, определенный в стандарте C ++ 11. При одинаковых параметрах шаблона и одинаковых параметрах инициализации разные объекты будут генерировать точно такую ​​же выходную последовательность для каждого объекта на любом компьютере в любом приложении, созданном с помощью стандартной библиотеки, совместимой с C ++ 11. Преимущество этого класса - предсказуемо высокое качество выходной последовательности и полная согласованность между реализациями.

Также существует больше механизмов PRNG, определенных в стандарте C ++ 11 - std :: linear_congruential_engine <> (исторически используется в качестве srand/randалгоритма хорошего качества в некоторых реализациях стандартной библиотеки C) и std :: subtract_with_carry_engine <> . Они также генерируют полностью определенные зависящие от параметров выходные последовательности для каждого объекта.

Современный пример замены C ++ 11 устаревшего кода C выше:

#include <iostream>
#include <chrono>
#include <random>

int main()
{
    std::random_device rd;
    // seed value is designed specifically to make initialization
    // parameters of std::mt19937 (instance of std::mersenne_twister_engine<>)
    // different across executions of application
    std::mt19937::result_type seed = rd() ^ (
            (std::mt19937::result_type)
            std::chrono::duration_cast<std::chrono::seconds>(
                std::chrono::system_clock::now().time_since_epoch()
                ).count() +
            (std::mt19937::result_type)
            std::chrono::duration_cast<std::chrono::microseconds>(
                std::chrono::high_resolution_clock::now().time_since_epoch()
                ).count() );

    std::mt19937 gen(seed);

    for( unsigned long j = 0; j < 100500; ++j )
    /* ^^^Yes. Generating single pseudo-random number makes no sense
       even if you use std::mersenne_twister_engine instead of rand()
       and even when your seed quality is much better than time(NULL) */    
    {
        std::mt19937::result_type n;
        // reject readings that would make n%6 non-uniformly distributed
        while( ( n = gen() ) > std::mt19937::max() -
                                    ( std::mt19937::max() - 5 )%6 )
        { /* bad value retrieved so get next one */ }

        std::cout << n << '\t' << n % 6 + 1 << '\n';
    }

    return 0;
}

Версия предыдущего кода, в котором используется std :: uniform_int_distribution <>

#include <iostream>
#include <chrono>
#include <random>

int main()
{
    std::random_device rd;
    std::mt19937::result_type seed = rd() ^ (
            (std::mt19937::result_type)
            std::chrono::duration_cast<std::chrono::seconds>(
                std::chrono::system_clock::now().time_since_epoch()
                ).count() +
            (std::mt19937::result_type)
            std::chrono::duration_cast<std::chrono::microseconds>(
                std::chrono::high_resolution_clock::now().time_since_epoch()
                ).count() );

    std::mt19937 gen(seed);
    std::uniform_int_distribution<unsigned> distrib(1, 6);

    for( unsigned long j = 0; j < 100500; ++j )
    {
        std::cout << distrib(gen) << ' ';
    }

    std::cout << '\n';
    return 0;
}

Я спросил подобный вопрос здесь ссылка , но все еще не могли найти четкий ответ. Не могли бы вы продемонстрировать: «На самом деле вы должны вызвать srand (seed) один раз, а затем вызвать rand ()» с кодами, потому что я уже сделал то, что вы говорите, но он не работает должным образом.
Башбурак

2
@bashburak Похоже, вы полностью упустили суть этого ответа. Почему именно вы урезали мою цитату? В своем ответе я сказал буквально: «На самом деле вы должны вызвать srand (seed) один раз, а затем вызвать rand () много раз и проанализировать эту последовательность - она ​​должна выглядеть случайной». Вы заметили, что вам следует вызывать rand () МНОГО РАЗ после одного вызова srand (...)? Ваш вопрос в вашей ссылке является точной копией этого вопроса с таким же недоразумением.
Серж Дундич

Это старый ответ, но он появляется, когда вы гуглите «Генерация случайных чисел C ++». Это плохой совет для программистов на C ++, потому что он советует вам использовать rand()и srand(). Вы можете его обновить?
Якк - Адам Неврамонт,

@ Yakk-AdamNevraumont Собственно и не советую использовать rand()и srand(). Фактически он просто отвечает на вопрос с предоставленным описанием. Из описания (в котором используется rand/ srand) очевидно, что необходимо объяснить основные концепции генерации псевдослучайных чисел - например, сам смысл псевдослучайной последовательности и ее начального числа. Я стараюсь делать именно это и использовать наиболее простую и знакомую rand/ srandкомбинацию. Забавно то, что некоторые другие ответы - даже с очень большим рейтингом - страдают от тех же недоразумений, что и автор вопроса.
Серж Дундич

@ Yakk-AdamNevraumont Я прислушался к вашему совету и внес в свой ответ некоторую информацию о новейших дополнениях C ++. Хотя я считаю это немного не по теме, но ваше предложение, а также некоторые другие ответы указывают на то, что как старые добрые, так std::rand/std::srandи новые функции библиотеки C ++, такие какstd::random_device<> , станд :: mersenne_twister_engine <> и множество случайных распределений требуют некоторого пояснения.
Серж Дундич

223

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

Попробуйте некоторые функции C ++ 11 для лучшего распространения:

#include <random>
#include <iostream>

int main()
{
    std::random_device dev;
    std::mt19937 rng(dev());
    std::uniform_int_distribution<std::mt19937::result_type> dist6(1,6); // distribution in range [1, 6]

    std::cout << dist6(rng) << std::endl;
}

См. Этот вопрос / ответ для получения дополнительной информации о случайных числах C ++ 11. Вышеупомянутый не единственный способ сделать это, но это один из способов.


8
Величина смещения, вносимого использованием %6, исчезающе мала. Может быть, важно, если вы пишете игру в кости для использования в Лас-Вегасе, но не имеет никакого значения практически в любом другом контексте.
Hot Licks

9
HotLicks: согласен, но если вы используете версию C ++, которая поддерживает random_deviceи mt19937уже поддерживает , буквально нет причин не делать все возможное и использовать стандарт uniform_int_distribution.
Quuxplusone

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

4
Rng для "диапазона"?
Christoffer

5
@ ChristofferHjärtström: Это для г andom п умбра г enerator.
Cornstalks

13

Если вы используете библиотеки boost, вы можете получить случайный генератор следующим образом:

#include <iostream>
#include <string>

// Used in randomization
#include <ctime>
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_int_distribution.hpp>
#include <boost/random/variate_generator.hpp>

using namespace std;
using namespace boost;

int current_time_nanoseconds(){
    struct timespec tm;
    clock_gettime(CLOCK_REALTIME, &tm);
    return tm.tv_nsec;
}

int main (int argc, char* argv[]) {
    unsigned int dice_rolls = 12;
    random::mt19937 rng(current_time_nanoseconds());
    random::uniform_int_distribution<> six(1,6);

    for(unsigned int i=0; i<dice_rolls; i++){
        cout << six(rng) << endl;
    }
}

Где функция current_time_nanoseconds()дает текущее время в наносекундах, которое используется в качестве начального числа.


Вот более общий класс для получения случайных целых чисел и дат в диапазоне:

#include <iostream>
#include <ctime>
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_int_distribution.hpp>
#include <boost/random/variate_generator.hpp>
#include "boost/date_time/posix_time/posix_time.hpp"
#include "boost/date_time/gregorian/gregorian.hpp"


using namespace std;
using namespace boost;
using namespace boost::posix_time;
using namespace boost::gregorian;


class Randomizer {
private:
    static const bool debug_mode = false;
    random::mt19937 rng_;

    // The private constructor so that the user can not directly instantiate
    Randomizer() {
        if(debug_mode==true){
            this->rng_ = random::mt19937();
        }else{
            this->rng_ = random::mt19937(current_time_nanoseconds());
        }
    };

    int current_time_nanoseconds(){
        struct timespec tm;
        clock_gettime(CLOCK_REALTIME, &tm);
        return tm.tv_nsec;
    }

    // C++ 03
    // ========
    // Dont forget to declare these two. You want to make sure they
    // are unacceptable otherwise you may accidentally get copies of
    // your singleton appearing.
    Randomizer(Randomizer const&);     // Don't Implement
    void operator=(Randomizer const&); // Don't implement

public:
    static Randomizer& get_instance(){
        // The only instance of the class is created at the first call get_instance ()
        // and will be destroyed only when the program exits
        static Randomizer instance;
        return instance;
    }
    bool method() { return true; };

    int rand(unsigned int floor, unsigned int ceil){
        random::uniform_int_distribution<> rand_ = random::uniform_int_distribution<> (floor,ceil);
        return (rand_(rng_));
    }

    // Is not considering the millisecons
    time_duration rand_time_duration(){
        boost::posix_time::time_duration floor(0, 0, 0, 0);
        boost::posix_time::time_duration ceil(23, 59, 59, 0);
        unsigned int rand_seconds = rand(floor.total_seconds(), ceil.total_seconds());
        return seconds(rand_seconds);
    }


    date rand_date_from_epoch_to_now(){
        date now = second_clock::local_time().date();
        return rand_date_from_epoch_to_ceil(now);
    }

    date rand_date_from_epoch_to_ceil(date ceil_date){
        date epoch = ptime(date(1970,1,1)).date();
        return rand_date_in_interval(epoch, ceil_date);
    }

    date rand_date_in_interval(date floor_date, date ceil_date){
        return rand_ptime_in_interval(ptime(floor_date), ptime(ceil_date)).date();
    }

    ptime rand_ptime_from_epoch_to_now(){
        ptime now = second_clock::local_time();
        return rand_ptime_from_epoch_to_ceil(now);
    }

    ptime rand_ptime_from_epoch_to_ceil(ptime ceil_date){
        ptime epoch = ptime(date(1970,1,1));
        return rand_ptime_in_interval(epoch, ceil_date);
    }

    ptime rand_ptime_in_interval(ptime floor_date, ptime ceil_date){
        time_duration const diff = ceil_date - floor_date;
        long long gap_seconds = diff.total_seconds();
        long long step_seconds = Randomizer::get_instance().rand(0, gap_seconds);
        return floor_date + seconds(step_seconds);
    }
};

1
Теперь, когда у нас есть random как часть стандарта, я бы не рекомендовал использовать версию boost, если вы не используете действительно старый компилятор.
Мартин Йорк,

9
#include <iostream>
#include <cstdlib>
#include <ctime>

int main() {
    srand(time(NULL));
    int random_number = std::rand(); // rand() return a number between ​0​ and RAND_MAX
    std::cout << random_number;
    return 0;
}

http://en.cppreference.com/w/cpp/numeric/random/rand


Чем отличается код автора вопроса? (За исключением того, что вы не используете %6.) И если вы решили использовать std::randC ++ API randфункции библиотеки C, то почему бы не использовать std::timeи std::srandради единообразия стиля C ++?
Серж Дундич,

6

Когда вы выполняете базовый поиск random number generationв Интернете на языке программирования C ++, этот вопрос обычно появляется первым! Я хочу бросить свою шляпу, чтобы, надеюсь, лучше прояснить концепцию генерации псевдослучайных чисел. в C ++ для будущих программистов, которые неизбежно будут искать этот же вопрос в Интернете!

Основы

Генерация псевдослучайных чисел включает в себя процесс использования детерминированного алгоритма, который производит последовательность чисел, свойства которых приблизительно напоминают случайные числа . Я говорю приблизительно похожи , потому что истинная случайность - довольно неуловимая загадка в математике и информатике. Следовательно, почему термин псевдослучайный используется для большей педантичности!

Прежде чем вы действительно сможете использовать ГПСЧ, т. Е. pseudo-random number generatorВы должны предоставить алгоритму начальное значение, часто также называемое семенем . Тем не менее, семя должно быть установлено только один раз перед использованием самого алгоритма!

/// Proper way!
seed( 1234 ) /// Seed set only once...
for( x in range( 0, 10) ):
  PRNG( seed ) /// Will work as expected

/// Wrong way!
for( x in rang( 0, 10 ) ):
  seed( 1234 ) /// Seed reset for ten iterations!
  PRNG( seed ) /// Output will be the same...

Таким образом, если вам нужна хорошая последовательность чисел, вы должны предоставить достаточное количество начального числа для ГПСЧ!

Старый путь C

Стандартная библиотека C с обратной совместимостью, имеющаяся в C ++, использует так называемый линейный конгруэнтный генератор, находящийся в cstdlibфайле заголовка! Этот ГПСЧ функционирует посредством прерывистой кусочной функции, которая использует модульную арифметику, то есть быстрого алгоритма, который любит использовать modulo operator '%'. Ниже приводится общее использование этого PRNG в отношении исходного вопроса, заданного @Predictability:

#include <iostream>
#include <cstdlib>
#include <ctime>

int main( void )
{
  int low_dist  = 1;
  int high_dist = 6;
  std::srand( ( unsigned int )std::time( nullptr ) );
  for( int repetition = 0; repetition < 10; ++repetition )
    std::cout << low_dist + std::rand() % ( high_dist - low_dist ) << std::endl;
  return 0;
}

При обычном использовании ГПСЧ C возникает целый ряд проблем, таких как:

  1. Общий интерфейс std::rand()не очень интуитивно понятен для правильной генерации псевдослучайных чисел между заданным диапазоном, например, для создания чисел между [1, 6], как того требует @Predictability.
  2. Обычное использование std::rand()исключает возможность равномерного распределения псевдослучайных чисел из-за принципа голубятни .
  3. Обычный способ std::rand()просеивания std::srand( ( unsigned int )std::time( nullptr ) )технически неверен, потому что time_tсчитается ограниченным типом . Следовательно, преобразование из time_tв unsigned int не гарантируется!

Для получения более подробной информации об общих проблемах использования C PRNG и способах их обхода, пожалуйста, обратитесь к Использование rand () (C / C ++): Совет по функции rand () стандартной библиотеки C !

Стандартный способ C ++

С момента публикации стандарта ISO / IEC 14882: 2011, т. Е. C ++ 11, randomбиблиотека уже некоторое время находится отдельно от языка программирования C ++. Эта библиотека поставляется с несколькими ГПСЧ и различными типами распределения, такими как: равномерное распределение , нормальное распределение , биномиальное распределение и т. Д. Следующий пример исходного кода демонстрирует очень базовое использование randomбиблиотеки в отношении исходного вопроса @ Predictability:

#include <iostream>
#include <cctype>
#include <random>

using u32    = uint_least32_t; 
using engine = std::mt19937;

int main( void )
{
  std::random_device os_seed;
  const u32 seed = os_seed();

  engine generator( seed );
  std::uniform_int_distribution< u32 > distribute( 1, 6 );

  for( int repetition = 0; repetition < 10; ++repetition )
    std::cout << distribute( generator ) << std::endl;
  return 0;
}

32-битный Вихрь Мерсенна двигатель, с равномерным распределением по целочисленных значений был использован в приведенном выше примере. (Название движка в исходном коде звучит странно, потому что его название происходит от периода 2 ^ 19937-1). В примере также используется std::random_deviceдля заполнения движка, который получает свое значение из операционной системы (если вы используете систему Linux, то std::random_deviceвозвращает значение из /dev/urandom).

Обратите внимание, что вам не нужно использовать std::random_deviceдля засева какой-либо двигатель . Вы можете использовать константы или даже chronoбиблиотеку! Также не обязательно использовать 32-битную версию std::mt19937движка, есть и другие варианты ! Для получения дополнительной информации о возможностях randomбиблиотеки посетите cplusplus.com

В общем, программисты на C ++ не должны std::rand()больше использовать его не потому, что это плохо , а потому, что текущий стандарт предоставляет лучшие альтернативы, которые более просты и надежны . Надеюсь, многие из вас сочтут это полезным, особенно те из вас, кто недавно искал в Интернете generating random numbers in c++!


4

Здесь можно получить полный Randomerкод класса для генерации случайных чисел!

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

Что-то такое:

class Randomer {
    // random seed by default
    std::mt19937 gen_;
    std::uniform_int_distribution<size_t> dist_;

public:
    /*  ... some convenient ctors ... */ 

    Randomer(size_t min, size_t max, unsigned int seed = std::random_device{}())
        : gen_{seed}, dist_{min, max} {
    }

    // if you want predictable numbers
    void SetSeed(unsigned int seed) {
        gen_.seed(seed);
    }

    size_t operator()() {
        return dist_(gen_);
    }
};

Такой класс пригодится позже:

int main() {
    Randomer randomer{0, 10};
    std::cout << randomer() << "\n";
}

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


Вы бы не хотели повторно использовать генератор для всех ваших объектов Randomer? Тем более что создавать инициализацию и поддерживать ее состояние относительно дорого.
Мартин Йорк

3

Генерируйте каждый раз новое случайное число, а не одно и то же шесть раз подряд.

Сценарий использования

Я сравнил проблему предсказуемости с мешком из шести листов бумаги, на каждом из которых написано значение от 0 до 5. Каждый раз, когда требуется новое значение, из пакета вынимается листок бумаги. Если сумка пуста, номера кладутся обратно в сумку.

... из этого я могу создать своего рода алгоритм.

Алгоритм

Сумка обычно Collection . Я выбрал bool[](иначе известный как логический массив, битовая плоскость или битовая карта), чтобы взять на себя роль пакета.

Причина, по которой я выбрал bool[] заключается в том, что индекс каждого элемента уже является стоимостью каждого листа бумаги. Если бы на бумагах требовалось что-то еще написанное на них, я бы использовал Dictionary<string, bool>вместо этого. Логическое значение используется для отслеживания того, нарисовано ли число или нет.

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

Для того, чтобы выбрать следующую случайную величину Я использую for..loopдля сканирования через мешок индексов, и счетчик отсчитать , когда indexэто falseназывается NumberOfMoves.

NumberOfMovesиспользуется для выбора следующего доступного номера. NumberOfMovesсначала устанавливается случайное значение между 0и 5, потому что есть 0..5 доступных шагов, которые мы можем пройти через мешок. На следующей итерации NumberOfMovesустанавливается случайное значение между 0и 4, потому что теперь мы можем пройти через мешок 0..4 шагов. По мере использования чисел доступные числа уменьшаются, поэтому вместо этого мы используем их rand() % (RemainingNumberCount + 1)для вычисления следующего значения NumberOfMoves.

Когда NumberOfMovesсчетчик достигнет нуля, for..loopдолжно произойти следующее:

  1. Установите текущее значение таким же, как for..loopиндекс.
  2. Установите все числа в сумке на false.
  3. Отрыв от for..loop.

Код

Код для вышеуказанного решения выглядит следующим образом:

(поместите следующие три блока в основной файл .cpp один за другим)

#include "stdafx.h"
#include <ctime> 
#include <iostream>
#include <string>

class RandomBag {
public:
    int Value = -1;

    RandomBag() {
        ResetBag();

    }

    void NextValue() {
        int BagOfNumbersLength = sizeof(BagOfNumbers) / sizeof(*BagOfNumbers);

        int NumberOfMoves = rand() % (RemainingNumberCount + 1);

        for (int i = 0; i < BagOfNumbersLength; i++)            
            if (BagOfNumbers[i] == 0) {
                NumberOfMoves--;

                if (NumberOfMoves == -1)
                {
                    Value = i;

                    BagOfNumbers[i] = 1;

                    break;

                }

            }



        if (RemainingNumberCount == 0) {
            RemainingNumberCount = 5;

            ResetBag();

        }
        else            
            RemainingNumberCount--; 

    }

    std::string ToString() {
        return std::to_string(Value);

    }

private:
    bool BagOfNumbers[6]; 

    int RemainingNumberCount;

    int NumberOfMoves;

    void ResetBag() {
        RemainingNumberCount = 5;

        NumberOfMoves = rand() % 6;

        int BagOfNumbersLength = sizeof(BagOfNumbers) / sizeof(*BagOfNumbers);

        for (int i = 0; i < BagOfNumbersLength; i++)            
            BagOfNumbers[i] = 0;

    }

};

Консольный класс

Я создаю этот класс Console, потому что он упрощает перенаправление вывода.

Ниже в коде ...

Console::WriteLine("The next value is " + randomBag.ToString());

... можно заменить на ...

std::cout << "The next value is " + randomBag.ToString() << std::endl; 

... а затем Consoleпри желании этот класс можно удалить.

class Console {
public:
    static void WriteLine(std::string s) {
        std::cout << s << std::endl;

    }

};

Основной метод

Пример использования следующим образом:

int main() {
    srand((unsigned)time(0)); // Initialise random seed based on current time

    RandomBag randomBag;

    Console::WriteLine("First set of six...\n");

    randomBag.NextValue();

    Console::WriteLine("The next value is " + randomBag.ToString());

    randomBag.NextValue();

    Console::WriteLine("The next value is " + randomBag.ToString());

    randomBag.NextValue();

    Console::WriteLine("The next value is " + randomBag.ToString());

    randomBag.NextValue();

    Console::WriteLine("The next value is " + randomBag.ToString());

    randomBag.NextValue();

    Console::WriteLine("The next value is " + randomBag.ToString());

    randomBag.NextValue();

    Console::WriteLine("The next value is " + randomBag.ToString());

    Console::WriteLine("\nSecond set of six...\n");

    randomBag.NextValue();

    Console::WriteLine("The next value is " + randomBag.ToString());

    randomBag.NextValue();

    Console::WriteLine("The next value is " + randomBag.ToString());

    randomBag.NextValue();

    Console::WriteLine("The next value is " + randomBag.ToString());

    randomBag.NextValue();

    Console::WriteLine("The next value is " + randomBag.ToString());

    randomBag.NextValue();

    Console::WriteLine("The next value is " + randomBag.ToString());

    randomBag.NextValue();

    Console::WriteLine("The next value is " + randomBag.ToString());

    Console::WriteLine("\nThird set of six...\n");

    randomBag.NextValue();

    Console::WriteLine("The next value is " + randomBag.ToString());

    randomBag.NextValue();

    Console::WriteLine("The next value is " + randomBag.ToString());

    randomBag.NextValue();

    Console::WriteLine("The next value is " + randomBag.ToString());

    randomBag.NextValue();

    Console::WriteLine("The next value is " + randomBag.ToString());

    randomBag.NextValue();

    Console::WriteLine("The next value is " + randomBag.ToString());

    randomBag.NextValue();

    Console::WriteLine("The next value is " + randomBag.ToString());

    Console::WriteLine("\nProcess complete.\n");

    system("pause");

}

Пример вывода

Когда я запустил программу, я получил следующий результат:

First set of six...

The next value is 2
The next value is 3
The next value is 4
The next value is 5
The next value is 0
The next value is 1

Second set of six...

The next value is 3
The next value is 4
The next value is 2
The next value is 0
The next value is 1
The next value is 5

Third set of six...

The next value is 4
The next value is 5
The next value is 2
The next value is 0
The next value is 3
The next value is 1

Process complete.

Press any key to continue . . .

Заключительное заявление

Эта программа была написана с использованием Visual Studio 2017 , и я решил сделать ее Visual C++ Windows Console Applicationпроектом с использованием .Net 4.6.1.

Я не делаю здесь ничего особенного, поэтому код должен работать и в более ранних версиях Visual Studio.


Если это VS 2017, вы должны использовать самую последнюю версию стандартной библиотеки: en.cppreference.com/w/cpp/numeric/random . В настоящее время в этом примере используются функции случайной библиотеки C и «Нет никаких гарантий относительно качества созданной случайной последовательности».
Роберт

2

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

#include <iostream>
#include <cstdlib>
#include <ctime>
int rollDie();
using std::cout;
int main (){
    srand((unsigned)time(0));
    int die1;
    int die2;
    for (int n=10; n>0; n--){
    die1 = rollDie();
    die2 = rollDie();
    cout << die1 << " + " << die2 << " = " << die1 + die2 << "\n";
}
system("pause");
return 0;
}
int rollDie(){
    return (rand()%6)+1;
}

2

Этот код производит случайные числа от nдо m.

int random(int from, int to){
    return rand() % (to - from + 1) + from;
}

пример:

int main(){
    srand(time(0));
    cout << random(0, 99) << "\n";
}

2
Это не совсем ответ на вопрос.
HolyBlackCat 07

1
Вы этого не исправили. Суть вопроса в том, что если вы запускаете программу несколько раз в секунду, она генерирует одни и те же случайные значения. Ваш код тоже это делает.
HolyBlackCat 07

1
@HolyBlackCat Я проверил его несколько раз, он работает. Вы srand(time(0))раньше добавляли в основную функцию random(n, m)?
Amir Fo

1
Вы должны добавить srand(time(0))в основную функцию, а не в цикл for или внутри реализации функции.
Amir Fo

1
Я скопировал ваш код дословно. Вы запускали его несколько раз в секунду ?
HolyBlackCat 07

1

для случайного каждого RUN файла

size_t randomGenerator(size_t min, size_t max) {
    std::mt19937 rng;
    rng.seed(std::random_device()());
    //rng.seed(std::chrono::high_resolution_clock::now().time_since_epoch().count());
    std::uniform_int_distribution<std::mt19937::result_type> dist(min, max);

    return dist(rng);
}

1
Вы не должны создавать генератор несколько раз. Он поддерживает набор состояний, чтобы генерировать последовательность случайных чисел, которая имеет соответствующее распределение (чтобы оно выглядело случайным).
Мартин Йорк

-2

Вот простой случайный генератор с прибл. равная вероятность получения положительных и отрицательных значений около 0:

  int getNextRandom(const size_t lim) 
  {
        int nextRand = rand() % lim;
        int nextSign = rand() % lim;
        if (nextSign < lim / 2)
            return -nextRand;
        return nextRand;
  }


   int main()
   {
        srand(time(NULL));
        int r = getNextRandom(100);
        cout << r << endl;
        return 0;
   }
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.