如何在 C++ 中生成随机数? [英] How to generate a random number in C++?

查看:121
本文介绍了如何在 C++ 中生成随机数?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试用骰子制作游戏,我需要在其中包含随机数(以模拟骰子的侧面.我知道如何在 1 和 6 之间制作).使用

I'm trying to make a game with dice, and I need to have random numbers in it (to simulate the sides of the die. I know how to make it between 1 and 6). Using

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

using namespace std;

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

效果不佳,因为当我多次运行该程序时,这是我得到的输出:

doesn't work very well, because when I run the program a few times, here's the output I get:

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

所以我想要一个命令,每次都会生成一个不同的随机数,而不是连续 5 次相同的随机数.有没有可以执行此操作的命令?

So I want a command that will generate a different random number each time, not the same one 5 times in a row. Is there a command that will do this?

推荐答案

你的测试应用程序最根本的问题是你调用 srand 一次,然后调用 rand 一次时间和退出.

The most fundamental problem of your test application is that you call srand once and then call rand one time and exit.

srand 函数的重点是用随机种子初始化伪随机数序列.

The whole point of srand function is to initialize the sequence of pseudo-random numbers with a random seed.

这意味着如果您在两个不同的应用程序中(具有相同的 srand/rand 实现)然后您将获得完全相同的序列 rand() 值之后在两个应用程序中读取.

It means that if you pass the same value to srand in two different applications (with the same srand/rand implementation) then you will get exactly the same sequence of rand() values read after that in both applications.

但是在您的示例应用程序中,伪随机序列仅包含一个元素 - 从种子生成的伪随机序列的第一个元素等于 1 sec 精度的当前时间.那么您希望在输出中看到什么?

However in your example application pseudo-random sequence consists only of one element - the first element of a pseudo-random sequence generated from seed equal to current time of 1 sec precision. What do you expect to see on output then?

显然,当您碰巧在同一秒运行应用程序时 - 您使用相同的种子值 - 因此您的结果当然是相同的(正如 Martin York 在对该问题的评论中已经提到的那样).

Obviously when you happen to run application on the same second - you use the same seed value - thus your result is the same of course (as Martin York already mentioned in a comment to the question).

实际上,您应该调用 srand(seed) 一次,然后调用 rand() 多次 并分析该序列 - 它应该看起来随机.

Actually you should call srand(seed) one time and then call rand() many times and analyze that sequence - it should look random.

修正 1 - 示例代码:

好的,我明白了.显然口头描述是不够的(可能是语言障碍什么的... :)).

OK I get it. Apparently verbal description is not enough (maybe language barrier or something... :) ).

基于问题中使用的相同 srand()/rand()/time() 函数的老式 C 代码示例:

Old-fashioned C code example based on the same srand()/rand()/time() functions that was used in the question:

#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,	%d
", n, n % 6 + 1 );
    }

    return 0;
}

^^^ 那个程序单次运行的序列应该看起来是随机的.

^^^ THAT sequence from a single run of the program is supposed to look random.

请注意我不建议在生产中使用 rand/srand 函数,原因如下所述,我绝对不推荐不建议使用函数 time 作为随机种子,因为 IMO 已经很明显了.这些对于教育目的来说很好,有时可以说明这一点,但对于任何认真的使用,它们大多毫无用处.

Please NOTE that I don't recommend to use rand/srand functions in production for the reasons explained below and I absolutely don't recommend to use function time as a random seed for the reasons that IMO already should be quite obvious. Those are fine for educational purposes and to illustrate the point sometimes but for any serious use they are mostly useless.

修正 2 - 详细说明:

了解目前 C 或 C++ 标准特性(库函数或类)明确地产生实际随机数据(即由标准保证实际上是随机的)很重要.解决此问题的唯一标准功能是 std::random_device 不幸的是仍然不提供实际随机性的保证.

It is important to understand that as of now there is none C or C++ standard features (library functions or classes) producing actually random data definitively (i.e. guaranteed by the standard to be actually random). The only standard feature that approaches this problem is std::random_device that unfortunately still does not provide guarantees of actual randomness.

根据应用程序的性质,您应该首先决定是否真的需要真正随机(不可预测)的数据.值得注意的情况当您确实需要真正的随机性时是信息安全 - 例如生成对称密钥、非对称私钥、盐值、安全令牌等

Depending on the nature of application you should first decide if you really need truly random (unpredictable) data. Notable case when you do most certainly need true randomness is information security - e.g. generating symmetric keys, asymmetric private keys, salt values, security tokens, etc.

然而,安全级随机数是一个单独的行业,值得单独写一篇文章.我在我的这个答案中简要讨论了它们.

However security-grade random numbers is a separate industry worth a separate article. I'm briefly discussing them in this answer of mine.

在大多数情况下,伪随机数生成器就足够了 - 例如用于科学模拟或游戏.在某些情况下,甚至需要一致定义的伪随机序列 - 例如在游戏中,您可以选择在运行时生成完全相同的地图,以避免在您的发行版中存储大量数据.

In most cases Pseudo-Random Number Generator is sufficient - e.g. for scientific simulations or games. In some cases consistently defined pseudo-random sequence is even required - e.g. in games you may choose to generate exactly same maps in runtime to avoid storing lots of data in your distribution.

最初的问题和重复出现的大量相同/相似的问题(甚至许多误导的答案")表明,首先区分随机数和伪随机数并了解什么是伪随机数很重要首先是随机数序列,并且要意识到伪随机数生成器的使用方式与使用真随机数生成器的方式不同.

The original question and reoccurring multitude of identical/similar questions (and even many misguided "answers" to them) indicate that first and foremost it is important to distinguish random numbers from pseudo-random numbers AND to understand what is pseudo-random number sequence in the first place AND to realize that pseudo-random number generators are NOT used the same way you could use true random number generators.

直观地当您请求随机数时 - 返回的结果不应该依赖于之前返回的值,也不应该依赖于任何人之前都要求过任何东西,不应该依赖于什么时候通过什么过程,在什么计算机上,从什么发生器和在哪个星系要求它.这就是随机"这个词的意思毕竟——不可预测且独立于任何事物——否则它不再是随机的,对吧?有了这个直觉很自然地在网上搜索一些魔法咒语来获得任何可能的上下文中的这种随机数.

Intuitively when you request random number - the result returned shouldn't depend on previously returned values and shouldn't depend if anyone requested anything before and shouldn't depend in what moment and by what process and on what computer and from what generator and in what galaxy it was requested. That is what word "random" means after all - being unpredictable and independent of anything - otherwise it is not random anymore, right? With this intuition it is only natural to search the web for some magic spells to cast to get such random number in any possible context.

^^^ 在所有涉及">伪随机数生成器 - 尽管对于真随机数来说是合理的.

^^^ THAT kind of intuitive expectations IS VERY WRONG and harmful in all cases involving Pseudo-Random Number Generators - despite being reasonable for true random numbers.

虽然随机数"这个有意义的概念是存在(某种) - 没有伪随机数"这样的东西.伪随机数生成器实际上产生了伪随机数序列.

While the meaningful notion of "random number" exists (kind of) - there is no such thing as "pseudo-random number". A Pseudo-Random Number Generator actually produces pseudo-random number sequence.

伪随机序列实际上总是确定性的(由其算法和初始参数预先确定)——也就是说,它实际上没有任何随机性.

Pseudo-random sequence is in fact always deterministic (predetermined by its algorithm and initial parameters) - i.e. there is actually nothing random about it.

当专家谈论 PRNG 的质量时,他们实际上是在谈论生成序列(及其显着的子序列)的统计特性.例如,如果您通过轮流使用两个高质量的 PRNG 来组合它们 - 您可能会产生不良的结果序列 - 尽管它们分别生成了良好的序列(这两个良好的序列可能只是相互关联,因此组合起来很糟糕).

When experts talk about quality of PRNG they actually talk about statistical properties of the generated sequence (and its notable sub-sequences). For example if you combine two high quality PRNGs by using them both in turns - you may produce bad resulting sequence - despite them generating good sequences each separately (those two good sequences may simply correlate to each other and thus combine badly).

特别是 rand()/srand(s) 对函数提供了一个单一的每进程非线程安全(!)伪随机数序列实现定义的算法.函数 rand() 产生 [0, RAND_MAX] 范围内的值.

Specifically rand()/srand(s) pair of functions provide a singular per-process non-thread-safe(!) pseudo-random number sequence generated with implementation-defined algorithm. Function rand() produces values in range [0, RAND_MAX].

引自 C11 标准 (ISO/IEC 9899:2011):

Quote from C11 standard (ISO/IEC 9899:2011):

srand 函数使用参数作为新序列的种子后续调用 rand 将返回的伪随机数.如果srand 然后用相同的种子值调用,序列伪随机数应重复.如果 rand 在任何之前被调用对 srand 的调用,将生成相同的序列当 srand 第一次被调用时,种子值为 1.

The srand function uses the argument as a seed for a new sequence of pseudo-random numbers to be returned by subsequent calls to rand. If srand is then called with the same seed value, the sequence of pseudo-random numbers shall be repeated. If rand is called before any calls to srand have been made, the same sequence shall be generated as when srand is first called with a seed value of 1.

许多人合理地期望 rand() 会产生一个范围为 0RAND_MAX 的半独立均匀分布的数字序列.那么它肯定应该(否则它没用)但不幸的是,不仅标准不需要 - 甚至有明确的免责声明声明无法保证所产生的随机序列的质量".在某些历史案例中,rand/srand 实现的质量确实很差.即使在现代实现中它很可能已经足够好 - 但信任已被破坏并且不容易恢复.除了它的非线程安全特性之外,它在多线程应用程序中的安全使用变得棘手且有限(仍然有可能 - 您可以只从一个专用线程使用它们).

Many people reasonably expect that rand() would produce a sequence of semi-independent uniformly distributed numbers in range 0 to RAND_MAX. Well it most certainly should (otherwise it's useless) but unfortunately not only standard doesn't require that - there is even explicit disclaimer that states "there is no guarantees as to the quality of the random sequence produced". In some historical cases rand/srand implementation was of very bad quality indeed. Even though in modern implementations it is most likely good enough - but the trust is broken and not easy to recover. Besides its non-thread-safe nature makes its safe usage in multi-threaded applications tricky and limited (still possible - you may just use them from one dedicated thread).

新类模板std::mersenne_twister_engine<>(和其便利的 typedefs - std::mt19937/std::mt19937_64 与良好的模板参数组合)提供了per-object 伪随机数生成器定义在 C++11 标准中.使用相同的模板参数和相同的初始化参数,不同的对象将在使用 C++11 兼容标准库构建的任何应用程序中的任何计算机上生成完全相同的每个对象输出序列.此类的优势在于其可预测的高质量输出序列和跨实现的完全一致性.

New class template std::mersenne_twister_engine<> (and its convenience typedefs - std::mt19937/std::mt19937_64 with good template parameters combination) provides per-object pseudo-random number generator defined in C++11 standard. With the same template parameters and the same initialization parameters different objects will generate exactly the same per-object output sequence on any computer in any application built with C++11 compliant standard library. The advantage of this class is its predictably high quality output sequence and full consistency across implementations.

C++11 标准中还定义了更多 PRNG 引擎 - std::linear_congruential_engine<>(历史上在某些 C 标准库实现中用作公平质量的 srand/rand 算法)和 std::subtract_with_carry_engine.它们还生成完全定义的依赖于参数的每个对象输出序列.

Also there are more PRNG engines defined in C++11 standard - std::linear_congruential_engine<> (historically used as fair quality srand/rand algorithm in some C standard library implementations) and std::subtract_with_carry_engine<>. They also generate fully defined parameter-dependent per-object output sequences.

现代 C++11 示例替换上面过时的 C 代码:

Modern day C++11 example replacement for the obsolete C code above:

#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 << '	' << n % 6 + 1 << '
';
    }

    return 0;
}

使用std::uniform_int_distribution<>的先前代码版本一个>

The version of previous code that uses 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 << '
';
    return 0;
}

这篇关于如何在 C++ 中生成随机数?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆