srand() - 为什么只调用一次?

时间:2011-09-08 06:13:49

标签: c random srand

这个问题是关于这个问题的评论 Recommended way to initialize srand?第一条评论说应该只在应用程序中调用srand()。为什么会这样?

7 个答案:

答案 0 :(得分:95)

这取决于你想要达到的目标。

随机化作为具有起始值的函数执行,即种子

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

如果您尝试在每次需要随机值时设置种子,且种子数相同,则始终会获得相同的“随机”值。

种子通常取自当前时间,即time(NULL)中的秒数,因此如果您始终在取随机数之前设置种子,只要您调用该数字,就会得到相同的数字。 srand / rand combo多次在同一秒

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

然而,您可能会在一秒钟内多次运行您的应用程序(特别是如果它是一个短的,或命令行工具或类似的东西),那么您将不得不采取其他方式选择种子(除非你在不同的应用程序实例中使用相同的序列)。但就像我说的那样,这取决于你的应用程序使用环境。

此外,您可能希望尝试将精度提高到微秒(最小化相同种子的几率),需要(sys/time.h):

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

答案 1 :(得分:22)

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

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

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

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。因此,如果在调用next之前每次设置rand值相同,它将返回相同的随机值,因为应用了相同的公式,这是不可取的,因为该函数是随机的。

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

答案 2 :(得分:7)

原因是srand()设置了随机生成器的初始状态,并且如果你没有自己触摸状态,那么生成器生成的所有值都只是“足够随机”。

例如你可以这样做:

int getRandomValue()
{
    srand(time(0));
    return rand();
}

然后如果你反复调用那个函数,以便time()在相邻的调用中返回相同的值,你就会得到相同的值 - 这是设计的。

答案 3 :(得分:7)

简短回答:为随机数生成器调用srand() 就像“滚动骰子”一样。也不像洗牌一样。如果有的话,更像是只剪一副牌。

这样想。 rand()来自一大堆卡片,每当你打电话时,它所做的就是从牌组顶部挑出下一张牌,给你价值,然后将牌返回到牌组的底部。 (是的,这意味着“随机”序列会在一段时间后重复。但它是非常大牌,通常是:4,294,967,296张牌。)

此外,每次你的程序运行时,都会从游戏商店购买一揽子全新的卡片,每个全新的卡片包都会有相同的序列。因此,除非你做一些特别的事情,否则每次你的程序运行时,它都会从rand()返回完全相同的“随机”数字。

现在,你可能会说,“好吧,那我怎么洗牌?”答案是(至少就randsrand而言),没有办法改变套牌。

那么srand做了什么?根据我在这里建立的类比,调用srand(n)基本上就像是说“从顶部剪切卡片n”。但是等等,还有一件事:它实际上是采取另一个全新的套牌并从顶部切割n张卡片。

因此,如果您每次拨打srand(n)rand()srand(n)rand(),...,n,则不会只是得到一个非常随机的序列,你实际上每次都会从rand()获得相同的数字。 (不一定是您递送到srand的相同号码,但是rand一遍又一遍的相同号码。)

所以你能做的最好的事情就是在你的程序开始时用一个srand()来合理地切割一次,即调用n一次recipient_id随机,这样你每次程序运行时都会从大牌中的不同随机位置开始。

[P.S。是的,我知道,在现实生活中,当你购买一副全新的卡片时,它通常是按顺序排列的,而不是随机排列的。对于这里的类比,我想象你从游戏商店购买的每个牌组都是看似随机的顺序,但是与你在同一商店购买的其他牌组完全相同的看似随机的顺序。有点像他们在桥牌锦标赛中使用的相同洗牌的牌组。]

答案 4 :(得分:2)

srand seed伪随机数生成器。如果您多次调用它,您将重新设置RNG。如果你使用相同的参数调用它,它将重新启动相同的序列。

要证明这一点,如果你做了一些简单的事情:

#include <cstdlib>
#include <cstdio>
int main() {
for(int i = 0; i != 100; ++i) {
        srand(0);
        printf("%d\n", rand());
    }
}

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

答案 5 :(得分:2)

使用/为同一秒运行的应用程序实例生成不同种子的更简单的解决方案如下所示。

srand()

这种方法使你的种子非常接近随机,因为没有办法猜测你的线程开始的时间和pid也会不同。

答案 6 :(得分:0)

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

2 \如果srand()运行多次,问题是如果两次运行发生在一秒钟内(时间(NULL)没有改变),下一个rand()将与rand()相同在上一个srand()之后。