srand() — 为啥只调用一次?

Posted

技术标签:

【中文标题】srand() — 为啥只调用一次?【英文标题】:srand() — why call it only once?srand() — 为什么只调用一次? 【发布时间】:2011-11-12 17:12:36 【问题描述】:

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

【问题讨论】:

尝试循环调用 srand 然后调用 rand 另见 Dilbert 的Tour of Accounting。 【参考方案1】:

简答:调用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是相当随机的,所以每次你的程序运行时,你都会从大牌组的一个不同的随机位置开始。使用rand(),这确实是您能做的最好的事情。

[附注是的,我知道,在现实生活中,当你购买一副全新的纸牌时,它通常是按顺序排列的,而不是随机排列的。为了让这个类比起作用,我想象你从游戏商店购买的每一副牌都按照看似随机的顺序排列,但与你从同一家商店购买的所有其他牌组的看似随机的顺序完全相同。有点像他们在桥牌比赛中使用的相同洗牌的牌组。]


附录:对于一个给定的 PRNG 算法和给定的种子值,你总是得到相同的序列的一个非常可爱的演示,请参阅this question(这是关于 Java,而不是 C,但无论如何)。

【讨论】:

精湛的解释史蒂夫。【参考方案2】:

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

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

此方法使您的种子非常接近随机,因为无法猜测您的线程何时开始,并且 pid 也会不同。

【讨论】:

【参考方案3】:

这取决于您要达到的目标。

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

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

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

种子通常取自当前时间,也就是秒,如time(NULL),所以如果你总是在取随机数之前设置种子,只要调用srand/就会得到相同的数字rand 组合多次在同一秒内

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

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

另外,您可能想尝试将精度提高到微秒(最大限度地减少相同种子的机会),需要 (sys/time.h):

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

【讨论】:

旁注:gettimeofday 在 POSIX 2008 中已过时。相反,它引入了 clock_gettime,这可能需要与 -lrt 链接。不过,它可能尚未在许多平台上可用。在 Linux 中,这没问题。在 Mac 上,我认为它尚不可用。在 Windows 中,它可能永远不可用。 t1.tv_usec 是一个长整数,而 srand 将一个无符号整数作为输入。 (而我刚刚遇到了一个问题。) 成功了。通过提高精度,它摆脱了我的重复。非常感谢非常。我有交货期限,这节省了我的臀部。【参考方案4】:

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

2\如果 srand() 运行多次,问题是如果两次运行发生在一秒钟内(时间(NULL)不变),下一个 rand() 将与 rand() 一样对在前一个 srand() 之后。

【讨论】:

重点是使用srand() 多次初始化相同的种子将导致rand() 返回相同的值。【参考方案5】:

随机数实际上是伪随机的。首先设置一个种子,每次调用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 的种子值设置为。因此,如果您在调用 rand 之前每次都将 next 值设置为相同,它将返回相同的随机值,因为它应用了相同的公式,这是不可取的,因为函数是随机的。

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

【讨论】:

你不是说 mysrand() 的参数(无符号长种子)吗? @Jiminion 这是来自man srand 的代码sn-p。范围是从 0 到 32767(假设 RAND_MAX),远小于 long 范围。状态变量next 变为long,因为内部乘法和加法将超出unsigned int 的范围。之后,在上述指定范围内对结果进行缩放或修改。虽然你可以做种子long. 请注意,C 标准也包括所示代码的 sn-p。【参考方案6】:

srand 为伪随机数生成器提供种子。如果您多次调用它,您将重新播种 RNG。如果您使用相同的参数调用它,它将重新启动相同的序列。

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

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

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

【讨论】:

问题是关于 C,而不是 C++。【参考方案7】:

原因是srand() 设置了随机生成器的初始状态,并且如果您不亲自接触该状态,则生成器生成的所有值都只是“足够随机”。

例如你可以这样做:

int getRandomValue()

    srand(time(0));
    return rand();

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

【讨论】:

以上是关于srand() — 为啥只调用一次?的主要内容,如果未能解决你的问题,请参考以下文章

KnockoutJs - 为啥初始化绑定处理程序只被调用一次?

为啥析构函数被调用两次而构造函数只被调用一次?

为啥 gprof 告诉我一个只从 main() 调用一次的函数被调用了 102 次?

为啥 Chrome 的 img 元素的 onerror 事件只触发一次?

为啥说安卓activity的onCreate()方法 只调用一次

为啥我的不和谐机器人只执行一次我的命令,而且只执行一次?