第一个随机数总是小于其余的

Posted

技术标签:

【中文标题】第一个随机数总是小于其余的【英文标题】:First random number is always smaller than rest 【发布时间】:2015-08-06 10:48:27 【问题描述】:

我碰巧注意到,在 C++ 中,使用 std rand() 方法调用的第一个随机数大部分时间都比第二个小得多。关于 Qt 实现,第一个几乎总是小几个数量级。

qsrand(QTime::currentTime().msec());
qDebug() << "qt1: " << qrand();
qDebug() << "qt2: " << qrand();

srand((unsigned int) time(0));
std::cout << "std1: " << rand() << std::endl;
std::cout << "std2: " << rand() << std::endl;

输出:

qt1:  7109361
qt2:  1375429742
std1: 871649082
std2: 1820164987

这是由于播种错误或错误导致的吗? 此外,虽然 qrand() 输出变化很大,但第一个 rand() 输出似乎随时间线性变化。只是想知道为什么。

【问题讨论】:

因为rand() 通常作为 LCG 实现,所以如果您在运行之间使用大小变化不大的种子(自 Epoch 以来的时间以秒为单位)为它播种,这是很正常的,播种后的第一个输出也将具有高度相关的量级。其他称为 escape from zeroland 的 PRNG 也存在类似问题,其中在将状态播种为 0 后的前几次迭代中,状态包含显着超过 50% 的 0。在许多情况下,解决方案是“预热” PRNG(或“逃离零地”):播种后,调用 PRNG 并丢弃它的前几个输出。 把它作为一个答案:) 【参考方案1】:

我不确定这是否可以归类为错误,但它有一个解释。让我们检查一下情况:

    查看rand's implementation。您会看到它只是使用最后生成的值进行的计算。

    您正在使用 QTime::currentTime().msec() 进行播种,它本质上受小范围值 0..999,但 qsrand 接受 uint 变量,范围为 0..4294967295

通过结合这两个因素,你就有了一个模式。

出于好奇:尝试使用 QTime::currentTime().msec() + 100000000

现在第一个值可能会大于第二个值。

我不会太担心。这种“模式”似乎只发生在前两个生成的值上。之后,一切似乎又恢复了正常。

编辑:

为了让事情更清楚,请尝试运行以下代码。它将使用所有可能的毫秒值(范围:0..999)作为种子,比较前两个生成的值以查看哪个更小:

int totalCalls, leftIsSmaller = 0;
for (totalCalls = 0; totalCalls < 1000; totalCalls++)

    qsrand(totalCalls);
    if (qrand() < qrand())
        leftIsSmaller++;

qDebug() << (100.0 * leftIsSmaller) / totalCalls;

它将打印 94.8,这意味着 94.8% 的时间第一个值会小于第二个值。

结论:当使用当前毫秒播种时,您会看到前两个值的模式。我在这里做了一些测试,生成第二个值后模式似乎消失了。我的建议:找到一个“好的”值来调用 qsrand (显然应该只在程序开始时调用一次)。一个好的值应该跨越 uint 类的整个范围。看看这个其他问题以获得一些想法:

Recommended way to initialize srand?

另外,看看这个:

PCG: A Family of Better Random Number Generators

【讨论】:

种子的小范围很重要,可能比开始时的确切模式更重要。即使没有可见的模式,也有很大的风险是程序的两次运行将使用完全相同的种子。【参考方案2】:

当前的 Qt 和 C 标准运行时都没有质量随机化器,您的测试显示。 Qt 似乎为此使用了 C 运行时(这很容易检查,但为什么)。如果您的项目中可以使用 C++ 11,请使用更好、更可靠的方法:

#include <random>
#include <chrono>

auto seed = std::chrono::system_clock::now().time_since_epoch().count();
std::default_random_engine generator(seed);
std::uniform_int_distribution<uint> distribution;
uint randomUint = distribution(generator);

有很好的video 涵盖了该主题。正如评论者 user2357112 所指出的,我们可以应用不同的随机引擎,然后应用不同的分布,但对于我的特定用途,上述效果非常好。

【讨论】:

您能解释一下是什么让这变得更好或更可靠吗? @user2357112,我看过这个:channel9.msdn.com/Events/GoingNative/2013/… 您能否在回答中包含视频的原因,而不仅仅是链接视频? 不,我的意思是包括视频给出的实际原因,而不仅仅是将链接放在答案中。除了使用不限于 0-999 范围的种子之外,我不明白为什么这个 sn-p 应该更好。我不认为该视频实际上支持您认为这更好的说法;该视频似乎支持使用特定的生成器,如std::mt19937,而该人特别不鼓励使用std::default_random_engine。见 29:20。 链接到这样的视频通常没有多大用处,因为当未来的访问者找到此帖子时,链接可能不会处于活动状态。我建议概述视频中提出的关键点并将其作为参考,而不仅仅是链接。【参考方案3】:

请记住,根据少量样本对统计现象做出判断可能会产生误导,因此我决定进行一个小型实验。我运行以下代码:

int main()

  int i = 0;
  int j = 0;
  while (i < RAND_MAX)
  
    srand(time(NULL));
    int r1 = rand();
    int r2 = rand();
    if (r1 < r2) 
      ++j;
    ++i;
    if (i%10000 == 0) 
      printf("%g\n", (float)j / (float)i);
    
  

它基本上打印了第一个生成的数字小于第二个的次数的百分比。您可以在下面看到该比率的图表:

如您所见,在不到 50 个实际新种子之后,它实际上接近 0.5。

按照评论中的建议,我们可以修改代码以在每次迭代时使用连续种子并加快收敛速度​​:

int main()

  int i = 0;
  int j = 0;
  int t = time(NULL);
  while (i < RAND_MAX)
  
    srand(t);
    int r1 = rand();
    int r2 = rand();
    if (r1 < r2)
      ++j;
    ++i;
    if (i%10000 == 0) 
      printf("%g\n", (float)j / (float)i);
    
    ++t;
  

这给了我们:

也非常接近 0.5。

虽然rand 肯定不是最好的伪随机数生成器,声称它在第一次运行期间通常会生成较小的数字似乎没有根据

【讨论】:

最好在循环外计算time(NULL),并在循环中手动增加种子。 这张图似乎表明,虽然第一个数字在一半以上的时间里不低于第二个,但它的运行时间非常长,每次都比硬币预期的要长得多-翻转。 @jwg 这不是程序连续多次使用完全相同的种子重新播种随机生成器的结果吗? @jwg 我同意这一点。还值得注意的是,种子的选择并不是均匀随机的。这个答案和问题都歪曲了srand 的输入,但它们的歪曲方式不同。 为了让事情更清楚,我也包含了连续种子的结果。

以上是关于第一个随机数总是小于其余的的主要内容,如果未能解决你的问题,请参考以下文章

软工概论第二周个人项目四则运算二(改进)

JavaScript 随机

js中random的应用

如何在javascript中随机产生一个8位数

Math.random()产生一个大于等于0小于1的随机数

明明的随机数