我尝试着创造一款带有骰子的游戏,我需要在其中添加一些随机数字(游戏邦注:即模拟骰子的边缘)。我知道如何在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次生成相同的随机数。是否有这样的命令?


当前回答

您的测试应用程序最基本的问题是,您调用srand一次,然后调用rand一次并退出。

srand函数的全部意义是用一个随机种子初始化伪随机数序列。

这意味着如果您在两个不同的应用程序(具有相同的srand/rand实现)中将相同的值传递给srand,那么您将在两个应用程序中获得完全相同的rand()值序列。

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

显然,当您恰好在同一秒运行应用程序时—您使用相同的种子值—因此您的结果当然是相同的(Martin York已经在对该问题的评论中提到过)。

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

修改1 -示例代码:

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

老式的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/srand函数,原因如下所述,我绝对不建议使用函数时间作为随机种子,原因IMO已经很明显了。这些用于教育目的,有时可以用来说明问题,但对于任何严肃的用途,它们大多是无用的。

修订2 -详细说明:

重要的是要明白,到目前为止,没有C或c++标准特性(库函数或类)最终产生实际随机的数据(即由标准保证实际上是随机的)。解决这个问题的唯一标准特性是std::random_device,不幸的是,它仍然不能保证实际的随机性。

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

实际上,安全级随机数是一个单独的行业,值得另写一篇文章。(在我的回答中,我简要地提到了这一点。)

在大多数情况下伪随机数生成器是足够的-例如科学模拟或游戏。在某些情况下,甚至需要一致定义的伪随机序列——例如,在游戏中,你可以每次在运行时生成相同的地图,以节省安装包的大小。

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

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.

^^^这种直观的期望在所有涉及伪随机数生成器的情况下都是非常错误和有害的——尽管对真随机数来说是合理的。

虽然“随机数”的有意义的概念存在(某种程度上)-没有“伪随机数”这样的东西。伪随机数发生器实际上产生伪随机数序列。

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

当专家们谈论PRNG的质量时,他们实际上是在谈论所生成序列(及其显著的子序列)的统计属性。例如,如果你轮流使用两个高质量的prng来组合它们,你可能会产生糟糕的结果序列,尽管它们各自产生了良好的序列(这两个良好的序列可能只是相互关联,因此组合不好)。

具体来说,rand()/srand(s)对函数提供了一个奇异的每进程非线程安全(!)伪随机数序列,由实现定义的算法生成。函数rand()生成范围为[0,RAND_MAX]的值。

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

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

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).

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<>。它们还生成完全定义的依赖参数的每个对象输出序列。

现代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;
}

其他回答

使用模数可能会在随机数中引入偏置,这取决于随机数生成器。更多信息请看这个问题。当然,在随机序列中得到重复的数字是完全可能的。

尝试一些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随机数的更多信息,请参阅此问题/答案。上面的方法并不是唯一的方法,但它是一种方法。

每次生成不同的随机数,而不是连续六次生成相同的随机数。

用例场景

我把“可预测性”的问题比作一袋六块纸,每个纸上写着一个从0到5的值。每当需要一个新的数值时,就会从袋子里抽出一张纸。如果袋子是空的,那么数字被放回袋子。

根据这个,我可以创建一个算法。

算法

一个包通常是一个集合。我选择了bool[](也称为布尔数组,位平面或位图)来扮演袋子的角色。

我选择bool[]的原因是,每一项的索引已经是每张纸的值。如果论文需要在它们上面写任何东西,那么我将使用Dictionary<string, bool>代替它。布尔值用于跟踪数字是否已经绘制。

一个名为RemainingNumberCount的计数器被初始化为5,当选择一个随机数时,计数器会向下计数。这样,我们就不必在每次想要画一个新数字时,都要计算还剩下多少张纸了。

为了选择下一个随机值,我使用for..循环来扫描索引包,并使用一个计数器来计数,当索引为false时,称为NumberOfMoves。

NumberOfMoves用于选择下一个可用号码。NumberOfMoves首先被设置为0到5之间的随机值,因为有0..我们可以通过袋子的5个可行步骤。在下一次迭代中,NumberOfMoves被设置为0到4之间的随机值,因为现在有0..我们可以用4步穿过袋子。在使用这些数字时,可用的数字会减少,因此我们使用rand() % (RemainingNumberCount + 1)来计算NumberOfMoves的下一个值。

当NumberOfMoves计数器为零时,for..循环应该如下所示:

将当前值设置为与..循环的索引。 将包中的所有数字设置为false。 停止for..循环。

Code

上述解决方案的代码如下:

(将以下三个块依次放入主.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编写的,我选择使用. net 4.6.1使其成为Visual c++ Windows控制台应用程序项目。

我在这里没有做任何特别的事情,所以代码应该也适用于早期版本的Visual Studio。

我知道如何在c++中生成随机数,而不使用任何标头,编译器intrinsic或任何东西。

#include <cstdio> // Just for printf
int main() {
    auto val = new char[0x10000];
    auto num = reinterpret_cast<unsigned long long>(val);
    delete[] val;
    num = num / 0x1000 % 10;
    printf("%llu\n", num);
}

在运行一段时间后,我得到了以下数据:

0: 5268
1: 5284
2: 5279
3: 5242
4: 5191
5: 5135
6: 5183
7: 5236
8: 5372
9: 5343

看起来是随机的。

工作原理:

现代编译器使用ASLR(地址空间布局随机化)防止缓冲区溢出。 你可以不使用任何库生成一些随机数,这只是为了好玩。不要那样使用ASLR。

可以从这里获得生成随机数的完整随机类代码!

如果你在项目的不同部分需要随机数,你可以创建一个单独的类Randomer来封装它里面的所有随机内容。

就像这样:

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。

这段代码产生从n到m的随机数。

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

例子:

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