关于C#:srand()-为什么只调用一次?

srand() — why call it only once?

这个问题是关于这个问题的评论
推荐初始化srand的方法? 第一条评论说srand()应该仅在应用程序中被调用。 为什么会这样呢?


这取决于您要实现的目标。

随机化是作为具有起始值的函数(即种子)执行的。

因此,对于相同的种子,您将始终获得相同的值序列。

如果您每次需要一个随机值时都尝试设置种子,并且种子是相同的数字,那么您将始终获得相同的"随机"值。

种子通常是从当前时间(秒)开始获取的,如time(NULL)所示,因此,如果始终在获取随机数之前设置种子,则只要调用srand / rand组合倍数,就将获得相同的数字。次在同一秒。

为避免此问题,每个应用程序只设置一次srand,因为怀疑两个应用程序实例将在同一秒内初始化,因此每个实例将具有不同的随机数序列。

但是,您极有可能在一秒钟内多次运行您的应用程序(特别是短程序,命令行工具或类似工具),那么您将不得不采用其他方法来选择种子(除非您可以在不同的应用程序实例中使用相同的顺序)。但是就像我说的那样,这取决于您的应用程序使用情况。

另外,您可能希望尝试将精度提高到微秒(以最小化相同种子的机会),要求(sys/time.h):

1
2
3
struct timeval t1;
gettimeofday(&t1, NULL);
srand(t1.tv_usec * t1.tv_sec);


随机数实际上是伪随机数。首先设置一个种子,每个rand调用都将从中获得一个随机数,并修改内部状态,此新状态将在下一个rand调用中使用以获取另一个数。由于使用某个公式来生成这些"随机数",因此在每次调用rand之后设置种子的某个值将在该调用中返回相同的数字。例如,srand (1234); rand ();将返回相同的值。用种子值初始化一次初始状态将生成足够的随机数,因为您没有使用srand设置内部状态,因此使这些数字更有可能是随机的。

通常,初始化种子值时,我们使用time (NULL)返回的秒值。说srand (time (NULL));处于循环中。然后,循环可以在一秒内迭代一次以上,因此循环在循环中第二次rand调用中在循环内迭代的次数将返回相同的"随机数",这是不希望的。在程序启动时对其进行一次初始化将设置一次种子,并且每次调用rand时,都会生成一个新数字并修改内部状态,因此下一次调用rand将返回一个足够随机的数字。

例如,来自http://linux.die.net/man/3/rand的以下代码:

1
2
3
4
5
6
7
8
9
static unsigned long next = 1;
/* RAND_MAX assumed to be 32767 */
int myrand(void) {
    next = next * 1103515245 + 12345;
    return((unsigned)(next/65536) % 32768);
}
void mysrand(unsigned seed) {
    next = seed;
}

内部状态next声明为全局。每个myrand调用都会修改并更新内部状态,并返回一个随机数。每次调用myrand将具有不同的next值,因此该方法将在每次调用时返回不同的数字。

查看mysrand实现;它只是将您传递的种子值设置为next。因此,如果在调用rand之前每次都将next值设置为相同,则将返回相同的随机值,因为应用在其上的公式相同,这是不希望的,因为该函数是随机的。

但是根据您的需要,您可以将种子设置为某个值,以在每次运行时生成相同的"随机序列",例如针对某个基准或其他基准。


简短的答案:对于随机数生成器,调用srand()并不像"掷骰子"。这也不像洗牌一样。如果有的话,这更像是切开一副纸牌。

这样想吧。 rand()从一大副纸牌中进行交易,每次调用时,它所做的就是从纸牌顶部拾取下一张纸牌,给您值,然后将该卡返回到纸牌底部。 (是的,这意味着"随机"序列将在一段时间后重复。不过,这是一个很大的套牌:通常为4,294,967,296张卡。)

此外,每次您的程序运行时,都会从游戏商店购买一包崭新的纸牌,并且每张崭新的纸牌始终具有相同的顺序。因此,除非您执行特殊操作,否则每次运行程序时,它都会从rand()返回完全相同的"随机"数字。

现在,您可能会说:"好吧,那我该如何洗牌呢?"答案-至少就randsrand而言-是没有办法洗牌。

那么srand的作用是什么?根据我在这里构建的类比,调用srand(n)基本上就像是说:"从顶部切下卡座n卡"。但是,还有一件事:它实际上是从另一个全新的卡座开始,并从顶部切下n卡。

因此,如果您每次以相同的n调用srand(n)rand()srand(n)rand(),...,您将不仅会得到一个不是非常随机的序列,实际上每次都从rand()返回相同的数字。 (可能不是您递给srand的相同号码,而是一遍又一遍地从rand返回的相同号码。)

因此,您最好的办法是切开卡座一次,也就是说,在程序开始时调用一次srand(),并使用一个合理随机的n,这样您便可以在其中的另一个随机位置开始每次程序运行时都会在大平台上运行。使用rand(),这确实是您可以做的最好的事情。

[附注:是的,我知道,在现实生活中,当您购买全新的卡片组时,通常是有顺序的,而不是随机的。为了进行类比,我想像一下,您从游戏商店购买的每个牌组都是随机排列的,但与您从同一家商店购买的其他纸牌完全一样,似乎是随机的。有点像他们在桥牌比赛中使用的相同洗牌的扑克牌。]


原因是srand()设置随机数生成器的初始状态,并且如果您自己不触摸它们之间的状态,则生成器生成的所有值仅"足够随机"。

例如,您可以这样做:

1
2
3
4
5
int getRandomValue()
{
    srand(time(0));
    return rand();
}

然后,如果您重复调用该函数,以使time()在相邻调用中返回相同的值,则只会得到生成的相同值-这是设计使然。


如图所示,一个更简单的解决方案是使用srand()为在同一秒运行的应用程序实例生成不同的种子。

1
srand(time(NULL)-getpid());

该方法使您的种子非常接近随机,因为无法猜测线程何时启动,并且pid也将有所不同。


srand种子伪随机数生成器。如果调用不止一次,则将重新播种RNG。如果使用相同的参数调用它,它将重新启动相同的序列。

为了证明这一点,如果您做一些简单的事情,例如:

1
2
3
4
5
6
7
8
9
#include <cstdlib>
#include <cstdio>
int main() {
for(int i = 0; i != 100; ++i) {
        srand(0);
        printf("%d
"
, rand());
    }
}

您将看到相同的数字被打印100次。


1 似乎每次rand()运行时,都会为下一个rand()设置新的种子。

2 如果srand()运行多次,则问题是如果两个运行在一秒钟内发生(时间(NULL)不变),则下一个rand()将与前一个rand()相同srand()函数。