当前位置: 首页 > 知识库问答 >
问题:

如何在C中生成随机数?

宰父远
2023-03-14

我想用骰子做一个游戏,我需要在其中加入随机数(以模拟骰子的侧面。我知道如何在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次生成相同的随机数。是否有命令可以执行此操作?

共有3个答案

公西星文
2023-03-14

每当您在C编程语言中对随机数生成进行基本Web搜索时,这个问题通常是第一个弹出的!我想把我的帽子扔进戒指里,希望能更好地澄清C中伪随机数生成的概念,以便未来的程序员不可避免地在网上搜索同样的问题!

伪随机数生成涉及使用确定性算法的过程,该算法生成一系列性质近似于随机数的数字。我说近似相似,因为真正的随机性在数学和计算机科学中是一个相当难以捉摸的谜。因此,为什么伪随机这一术语被用来更加学究式地正确!

在您可以实际使用PRNG之前,即伪随机数生成器,您必须为算法提供一个通常也称为种子的初始值。然而,在使用算法本身之前,种子只能设置一次!

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

因此,如果你想要一个好的数字序列,那么你必须为PRNG提供一个充足的种子!

C所拥有的向后兼容的标准C库,使用了cstdlib头文件中的线性同余生成器!此PRNG通过使用模运算的不连续分段函数运行,即喜欢使用模运算符“%”的快速算法。关于@Predictability提出的原始问题,以下是此PRNG的常见用法:

#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的PRNG的常见用法包含一系列问题,例如:

  1. std::rand()的整体界面对于在给定范围内正确生成伪随机数不是很直观,例如,按照@Predictability所需的方式生成[1,6]之间的数字
  2. 由于鸽子洞原理std::rand()的常见用法消除了伪随机数均匀分布的可能性
  3. 从技术上讲,通过std::srand((unsigned int)std:∶time(nullptr))播种标准::rand()的常见方法是不正确的,因为 被视为受限类型。因此,不能保证从 time_t转换为 无符号int

有关使用C的PRNG的总体问题以及如何可能规避它们的更多详细信息,请参考使用rand()(C/C):C标准库的rand()函数的建议!

自从ISO/IEC 14882:2011标准(即C 11)发布以来,random库已经成为C编程语言的一部分有一段时间了。该库配备了多个PRNG和不同的分布类型,例如:均匀分布、正态分布、二项分布等。以下源代码示例演示了随机库的基本用法,涉及@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位Mersenne Twister引擎,该引擎具有统一的整数值分布。(源代码中引擎的名称听起来很奇怪,因为它的名字来自于2^19937-1这段时间)。该示例还使用std::random_device为引擎设定种子,该引擎从操作系统获取其值(如果您使用的是Linux系统,则std:∶random_ddevice从 >返回一个值)。

请注意,您不必使用std::random_device来播种任何引擎。您可以使用常量甚至Chrono库!您也不必使用32位版本的std::mt19937引擎,还有其他选项!有关随机库功能的更多信息,请参阅cplusplus.com

总而言之,C程序员不应该再使用< code>std::rand()了,不是因为它不好,而是因为当前的标准提供了更好的替代方案,更直接、更可靠。希望你们中的许多人会觉得这很有帮助,尤其是那些最近在网上搜索了< code >用c语言生成随机数的人!

秦俊发
2023-03-14

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

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

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

然而,在您的示例应用程序中,伪随机序列仅包含一个元素-从seed生成的伪随机序列的第一个元素等于当前时间的< code>1秒精度。那么,您希望在输出中看到什么?

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

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

修订 1 - 示例代码:

好的,我明白了。显然,口头描述是不够的(也许是语言障碍或其他东西......:))。

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

#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,不幸的是,它仍然无法保证实际的随机性。

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

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

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

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

直观地说,当你请求随机数时 - 返回的结果不应该依赖于以前返回的值,也不应该取决于之前是否有人请求过任何东西,也不应该依赖于什么时刻,通过什么过程,什么计算机,从什么生成器和在哪个星系中请求它。这就是“随机”这个词毕竟意味着什么 - 不可预测且独立于任何东西 - 否则它就不再是随机的了,对吧?有了这种直觉,很自然地在网上搜索一些魔法咒语,以便在任何可能的环境中获得这样的随机数。

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

虽然“随机数”这个有意义的概念是存在的(有点),但“伪随机数”这种东西是不存在的。伪随机数发生器实际上产生伪随机数序列。

伪随机序列实际上总是确定性的(由其算法和初始参数预先确定) - 即它实际上没有任何随机性。

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

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

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

srand函数使用该参数作为伪随机数新序列的种子,该序列将通过后续调用rand返回。如果srand随后使用相同的种子值调用,则应重复伪随机数序列。如果rand在调用srand之前调用,则应生成与首次调用srand时相同的序列,种子值为1。

许多人合理地预期< code>rand()会产生一个范围在< code>0到< code>RAND_MAX的半独立均匀分布的数字序列。当然应该这样做(否则就没用了),但不幸的是,不仅标准不要求这样做,甚至还有明确的免责声明,声明“不能保证产生的随机序列的质量”。在某些历史案例中,< code > rand /< code > srand 实现的质量确实非常差。即使在现代的实现中,这很可能已经足够好了——但是信任被打破了,并且不容易恢复。此外,它的非线程安全特性使得它在多线程应用程序中的安全使用变得棘手和受限(仍然有可能——你可以只从一个专用线程中使用它们)。

新类模板std::mersenne_twister_engine

在C 11标准中还定义了更多的PRNG引擎

现代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;
}
蒲坚
2023-03-14

根据随机数发生器的不同,使用模运算可能会在随机数中引入偏差。更多信息请见此问题。当然,在随机序列中得到重复的数字是完全可能的。

尝试一些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随机数的更多信息,请参阅此问题/答案。以上并不是实现这一目标的唯一方法,而是一种方法。

 类似资料:
  • 本文向大家介绍C语言/C++如何生成随机数,包括了C语言/C++如何生成随机数的使用技巧和注意事项,需要的朋友参考一下 本文分享了C语言/C++如何生成随机数的具体实现方法,供大家参考,具体内容如下 C语言/C++怎样产生随机数:这里要用到的是rand()函数, srand()函数,C语言/C++里没有自带的random(int number)函数。 (1) 如果你只要产生随机数而不需要设定范围的

  • 我想使用c生成0-2^64范围内的非常大的随机数。我使用了rand()函数,但它没有生成非常大的数字。任何一个都可以帮助吗?

  • 问题内容: 在java中如何生成随机数? 问题答案: 在Java 1.7或更高版本中,执行此操作的标准方法如下: 请参阅相关的JavaDoc。这种方法的优点是不需要显式初始化java.util.Random实例,如果使用不当,可能会引起混乱和错误。 但是,相反,没有办法明确设置种子,因此在有用的情况下(例如测试或保存游戏状态或类似情况),很难重现结果。在这种情况下,可以使用下面显示的Java 1.

  • 我正在创建一个C-prog,它需要一个由17个整数组成的数组,所有整数都小于18并且是唯一的。这是我现在能做的: 我现在的算法是,每次rand()生成一个数字时,都会在数组中检查这个数字是否已经存在,如果已经存在,则rand()会继续生成一个数字,直到得到一个唯一的数字,然后将其存储在数组中,如果数组中不存在这样的数字,则直接输入到数组中。

  • 问题内容: Go的库缺少生成64位数字的功能。大约四年来这是一个未解决的问题。同时,解决方法是什么样的? 问题答案: 最简单的方法是拨打两次: 另一个选择是调用(在Go 1.7中添加了 )以读取8个字节,然后使用该包从中获取值: 注意:作为状态文档,它始终读取与传递的切片长度相同的字节,并且始终返回错误,因此在这种情况下无需检查错误。 注意#2:您也可以使用代替,因为我们正在使用其所有字节生成一个

  • 每当我看到这行代码 后面总是有一个很长的序列号..这个数字是如何产生的?如果我想随机生成这个值,我该怎么做呢?谢谢你的帮助。