【问题标题】:random number generation in C++ ... first number not very randomC ++中的随机数生成......第一个数字不是很随机
【发布时间】:2012-01-25 19:42:02
【问题描述】:

我试图在不使用 boost 的情况下在 C++ 中获得一个介于 0 和 1 之间的统一随机数。我不想依赖图书馆。

每当我开始我的程序时,我都会播种: srand(时间(NULL));

然后我打印 8 个随机数。我用空行分隔程序的不同运行:

Random number: 0.226063
Random number: 0.449186
Random number: 0.474514
Random number: 0.160779
Random number: 0.220868
Random number: 0.136685
Random number: 0.260120
Random number: 0.843334

Random number: 0.226181
Random number: 0.422253
Random number: 0.808594
Random number: 0.040531
Random number: 0.212377
Random number: 0.421073
Random number: 0.965790
Random number: 0.026305

Random number: 0.226306
Random number: 0.526858
Random number: 0.898279
Random number: 0.378934
Random number: 0.736653
Random number: 0.924420
Random number: 0.718503
Random number: 0.888140

Random number: 0.226463
Random number: 0.157614
Random number: 0.010386
Random number: 0.551936
Random number: 0.391998
Random number: 0.303603
Random number: 0.659396
Random number: 0.465434

为什么第一个数字每次都几乎完全相同?我不明白。我应该扔掉第一个数字还是什么?


示例代码:

#include <iostream>

int main() {
  srand( time(NULL) );
  printf("%f\n", (float)rand()/RAND_MAX);
  printf("%f\n", (float)rand()/RAND_MAX);
  printf("%f\n", (float)rand()/RAND_MAX);
  printf("%f\n", (float)rand()/RAND_MAX);
  printf("%f\n", (float)rand()/RAND_MAX);
  printf("%f\n", (float)rand()/RAND_MAX);
  printf("%f\n", (float)rand()/RAND_MAX);
  printf("%f\n", (float)rand()/RAND_MAX);
}

【问题讨论】:

  • 我应该扔掉第一个数字还是什么?显然是的。
  • 您能否提供一个完整的代码清单,说明您是如何生成值的。
  • 请记住,靠近的两个时间样本将具有相似的值。 RNG 在播种时会对种子进行卷积,但根据 RNG 算法,它可能无法在 2-3 个周期内实现完全随机性。最好丢弃一些数字(随机丢弃数是理想的)以创建最佳随机性。
  • 如果您不信任 RNG,那么您没有理由不根据已发布的算法编写自己的 RNG。一些较旧的“标准”RNG 是出了名的差。
  • 什么实现(编译器、运行时库、操作系统)?

标签: c++ random


【解决方案1】:

不,不要扔掉第一个。这会扭曲结果。序列{1,1,1,1,1,1,1}完全与任何其他任意七数序列一样可能出现,尽管人类倾向于在所有事物中寻找意义:-)

因为你不喜欢这个序列而试图摆弄它会使随机数生成更糟,不会更好。

不管怎样,你应该确保你的运行至少相隔一秒钟,这样你就不会使用相同的种子(这里似乎不是这种情况)。除此之外,按原样使用 PRNG 为您提供的结果或找到更好的生成器。

要么你是统计学家/密码​​学家,你不会使用普通的随机函数,要么这真的没关系!对于绝大多数情况,都是后者。


如果您不想要一个花哨的(或涉及大量额外内容的)并且您对您的实现提供的那个不满意,那么基于gcc 版本很容易实现一个,类似:

seed = (1103515245 * seed + 12345) & 0xffffffff
return seed & 0x7fffffff

请记住,初始种子值是根据提供给srand 的参数计算的,模数为2<sup>31</sup>-1,以最小化对初始种子具有线性依赖性的序列(序列仍然是线性的,只是没有从初始种子值)。

如果您只是在寻找快速解决方案而不依赖外部库或花时间实现更复杂的生成器,以下代码可能会让您的生活更轻松:

// Assume 32-bit integer.
static int seed = 1;
void mySRand (int newseed) {
    seed = newseed % 0x7fffffff;
}
int myRand() {
    seed = 1103515245 * seed + 12345;
    return seed & 0x7fffffff;
}

以下程序实际上会让您了解该算法将对提供给 mySRand 的种子值进行微小更改时会做什么。

它从time (NULL) 获取初始种子,然后显示myRand 中二十个连续种子值的初始值以及百分比变化。

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

static int seed = 1;
void mySRand (int newseed) { seed = newseed % 0x7fffffff; }
int myRand() { seed = 1103515245 * seed + 12345; return seed & 0x7fffffff; }

int main (void) {
    int i, xyzzy, val, lastVal;
    double avg, diff;
    xyzzy = time (NULL);
    mySRand (xyzzy);
    lastVal = myRand();
    printf ("seed=%d, val=%12d\n", xyzzy, lastVal);
    for (i = 0; i < 20; i++) {
        mySRand (++xyzzy);
        val = myRand();
        avg = val; avg = (avg + lastVal) / 2;
        diff = 100 * fabs (avg - val) / avg;
        printf ("seed=%d, val=%12d, avg=%12.1f, %%chg=%f\n",
            xyzzy, val, avg, diff);
        lastVal = val;
    }
    return 0;
}

百分比变化基于当前值与当前值与前一个平均值之间的差值,希望不会引入偏差。示例输出为:

seed=1324533721, val=  1092183454
seed=1324533722, val=    48215051, avg= 570199252.5, %chg=91.544175
seed=1324533723, val=  1151730296, avg= 599972673.5, %chg=91.963792
seed=1324533724, val=   107761893, avg= 629746094.5, %chg=82.888041
seed=1324533725, val=  1211277138, avg= 659519515.5, %chg=83.660545
seed=1324533726, val=   167308735, avg= 689292936.5, %chg=75.727484
seed=1324533727, val=  1270823980, avg= 719066357.5, %chg=76.732504
seed=1324533728, val=   226855577, avg= 748839778.5, %chg=69.705726
seed=1324533729, val=  1330370822, avg= 778613199.5, %chg=70.864150
seed=1324533730, val=   286402419, avg= 808386620.5, %chg=64.571108
seed=1324533731, val=  1389917664, avg= 838160041.5, %chg=65.829626
seed=1324533732, val=   345949261, avg= 867933462.5, %chg=60.141039
seed=1324533733, val=  1449464506, avg= 897706883.5, %chg=61.463005
seed=1324533734, val=   405496103, avg= 927480304.5, %chg=56.279815
seed=1324533735, val=  1509011348, avg= 957253725.5, %chg=57.639642
seed=1324533736, val=   465042945, avg= 987027146.5, %chg=52.884483
seed=1324533737, val=  1568558190, avg=1016800567.5, %chg=54.264095
seed=1324533738, val=   524589787, avg=1046573988.5, %chg=49.875518
seed=1324533739, val=  1628105032, avg=1076347409.5, %chg=51.262038
seed=1324533740, val=   584136629, avg=1106120830.5, %chg=47.190523
seed=1324533741, val=  1687651874, avg=1135894251.5, %chg=48.574735

因此您可以看到,基于靠近的初始种子,起始值实际上存在很大差异。

【讨论】:

  • 理论上是的,但实际上不是。如果您有真正的随机数来源(而不是随机数),我完全同意您的说法。
  • 我不同意。在这种特殊情况下,实现的rand() 似乎存在一些问题,因为第一个生成的数字与种子值的相关性过于可预测。丢弃第一个结果可能确实有意义。寻找更好的随机数生成器可能更有意义。
  • @paxdiablo:这是一个非常不正确和妄想的比较。像这样的答案如何获得177k?充其量你能做的就是计算汉明距离。
  • @Jared,我得到了 177k 的赞成票而不是反对票 :-) 如果您认为答案没有帮助,请投反对票。这就是 SO 的工作原理。目前,它是+5/-1,所以显然更多的人认为它有用,而不是那些不这么认为的人。如果我的答案在几周后的净票数是否定的,我会删除它们,因为社区认为它们不合适。我做的是根据一个人的cmets删除答案。但是,如果一个或多个人说服我相信它的不足之处,我经常会更改答案。
  • On wikipedia 表示 GCC 使用 31 位进行取模(可能与有符号整数有关)并且仅返回 30 位,因此不应该是这些值:0x7fffffff0x3fffffff ?我还在互联网上使用一些种子和 LCG 参数搜索(比较)一些生成的样本(前几个数字),但一无所获。是否有任何页面生成数字,例如乘数 1103515245 * 种子(例如 0)+ 增量 12345
【解决方案2】:

这很正常。 PRNG 必须预热。我脑后的数字大约是 1000。这意味着,在播种 PRNG 后,获得 1000 个数字并扔掉它们。

原因在于大多数生成器是如何实现的。它们通常类似于x = a*x+b,其中ab 是常量。所以,如果你不走运,你的种子(在你的情况下非常接近!)被选择,这样等式的第一部分与结果没有太大的相关性(即接近 0(mod MAX_RAND))。这就是你必须热身的原因:它消除了你选择的种子的相似性。这听起来很愚蠢,但这就是 PRNG 的工作方式(你可能会扔掉 50 而不是 1000,YMMV)。


顺便说一句,使用rand 通常是一个糟糕的主意。不仅如此(出于老实说让我无法理解的原因),与其他 PRNG 相比,它相当慢,而且它产生的数字也很差(在熵、周期性等方面)。如果您不想使用 boost,也许您可​​以使用 gsl,它几乎可以满足您的所有需求(关于随机数)。

【讨论】:

  • 只有在您的 PRNG 脑死亡时才有可能。 a 术语应该足够大,这样初始种子的微小差异就会导致初始值的巨大差异。
  • @paxdiablo:除了rand 相当脑残的概念之外,我完全看到a*x 术语的解释非常薄弱,但这就是迄今为止我对此事的最佳理解。我同意,可能有一种更令人信服的方式来解释它。
  • 如果你检查一个像样的 LC 生成器(GNU 的 a=1103515245, c=12345, seed_modulus=32bits, output=lower 30 bits),种子 27 和 28(随机挑选)会产生两个输出 803894712 和 833668133,点差约为 3%。这不是大量,但足够随机。我的观点是你需要一个非常好的 RNG(在这种情况下,买一个)或者你只使用你拥有的那个,这对于那些不需要真正的 RNG 的人来说通常绰绰有余:-)
  • @bitmask 我很确定在这种情况下,糟糕的 PRNG“热身”只会产生更好的 错觉,而良好的 PRNG 则不会问题。
  • 我向 glibc 道歉,它实际上使用了较低的 31(不是 30)位,因此 27/28 种子为您提供 1877636536/833668133,方式的差异 超过 3%。
【解决方案3】:

您可以使用标准库,它提供了高质量的 PRNG 引擎以及适当的分发适配器:

#include <random>

typedef std::mt19937 rng_type;
std::uniform_real_distribution<double> u01dist;

rng_type rng;

int main()
{
  rng.seed(std::time(NULL));

  double random_number = u01dist(rng);

  // ...
}

【讨论】:

  • 这是否仅限于特定版本的 C++? r.cpp:4:错误:命名空间“std”中的“mt19937”未命名类型
  • @gnychis,我认为这是他们在 C++03 的 TR1 中添加的内容。 TR1 本身不是标准,而是扩展指南,但我很确定它已进入 C++11。您的特定编译器可能不支持该标准,或者可能需要标志来启用它。请参阅en.wikipedia.org/wiki/… 或一些详细信息。
  • 您应该将-std=c++0x 添加到您的GCC。在旧版本中,您可能必须改用 &lt;tr1/random&gt;
【解决方案4】:

当我稍微调整您的示例以在编译为 C 时运行(我对 C++ 的了解不够好,无法在不诅咒的情况下修复编译错误)我只看到随机的第一行:

$ while true ; do sleep 1 ; ./rand | head -1 ; done
0.493923
0.353780
0.217848
0.570592
0.430408
0.290481
0.651497
0.006394
0.865017
0.721335
0.581914
0.936602
0.796496
^C

【讨论】:

  • 我得到了类似的东西。当没有模式时,人类非常善于发现模式
【解决方案5】:

可能程序执行之间的延迟太短,因此时间函数可能返回彼此过于相似的种子。

如果不知道 srand 函数是如何实现的,很难确定,但它是一个伪随机生成器,它将为同一个种子输出相同的序列以进行多次执行。尝试互相喂食延迟较大的种子,或者在 time 函数返回的时间上添加一个变量 padding,看看这是否足够影响输出。但是,请注意它们不是真正的随机数。

【讨论】:

    猜你喜欢
    • 2013-01-31
    • 2022-11-21
    • 2023-01-03
    • 2013-11-12
    • 2012-09-24
    • 1970-01-01
    • 2011-09-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多