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

First random number is always smaller than rest

我偶然注意到,在 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() 输出似乎随时间线性变化。只是想知道为什么。

当前的 Qt 和 C 标准 运行-time 都没有质量随机发生器,您的测试显示。 Qt 似乎为此使用 C 运行-time(这很容易检查,但为什么)。如果 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 所指出的,我们可以应用不同的随机引擎,然后应用不同的分布,但对于我的特定用途,上述方法非常有效。

我不确定这是否可以 class 确定为错误,但它有一个解释。让我们来看看情况:

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

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

结合这两个因素,您就有了一个模式。

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

进行播种

现在大多数时候第一个值可能会比第二个大。

我不会太担心。 "pattern" 似乎只发生在前两个生成的值上。在那之后,一切似乎都恢复了正常。

编辑:

为了使事情更清楚,请尝试 运行 下面的代码。它将使用所有可能的毫秒值(范围: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% 的时间第一个值将小于第二个。

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

  • Recommended way to initialize srand?

另外,看看这个:

考虑到根据少量样本对统计现象做出判断可能会产生误导,我决定 运行 进行一个小实验。我运行下面的代码:

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 肯定不是最好的伪随机数生成器,它经常在第一个 运行 期间生成较小数字的说法似乎没有根据