推荐初始化srand的方法?


68

我需要一种“好的”方式来初始化C ++中的伪随机数生成器。我发现一篇文章指出:

为了生成类似随机数的数字,通常将srand初始化为一些与众不同的值,例如与执行时间相关的值。例如,函数时间(在头文件ctime中声明)返回的值每秒都是不同的,对于大多数随机需求而言,这是足够独特的。

对于我的应用程序而言,Unixtime不够鲜明。有什么更好的初始化方法?如果它是可移植的,则有很多好处,但是代码将主要在Linux主机上运行。

我当时在考虑做一些pid / unixtime数学来获取整数,或者可能从中读取数据/dev/urandom

谢谢!

编辑

是的,我实际上每秒多次启动我的应用程序,并且遇到了冲突。


2
为什么time()还不够?您是否每秒多次启动该应用程序?请注意,您仅应在应用程序中调用srand()ONCE。
马丁·约克

2
如果time()或gettimeofday()不够,那么rand()可能对您来说不够好。PRNG根本不需要很好。加密随机性很难-使用加密库。
乔纳森·莱夫勒

6
实际上,至少MS CRT将种子保留在TLS中,因此在这种情况下,您实际上需要为每个线程初始化srand()ONCE。虽然不确定GCC如何存储种子。
Andreas Magnusson,

1
有关以微秒为单位初始化srand()的更多信息:guyrutenberg.com/2007/09/03/seeding-srand
没什么特别的,这里

1
相关:我如何入侵Hacker News。HN使用毫秒为单位的时间作为种子,然后使用RNG生成登录cookie。攻击者可以通过登录其帐户并接收Cookie来恢复种子,然后枚举可能的种子并针对Cookie对其进行检查,直到找到匹配项为止。有了正确的种子,他们便可以预测其他用户的cookie,并以这些用户的身份在网站上执行操作。
贝恩

Answers:


57

最好的答案是使用Boost随机数的东西。或者,如果您有权访问C ++ 11,请使用<random>标头。

但是,如果我们正在谈论rand()srand()
最好的方法就是使用time()

int main()
{
    srand(time(NULL));

    ...
}

确保在程序开始时执行此操作,而不是每次调用时都执行此操作rand()

每次启动时,time()都会返回一个唯一值(除非您每秒多次启动该应用程序)。在32位系统中,它将仅每60年左右重复一次。

我知道您认为时间不够独特,但我很难相信。但众所周知我错了。

如果同时启动许多应用程序副本,则可以使用分辨率更高的计时器。但是,这样一来,您可能会在较短的时间内重复输入该值。

好的,所以如果您真的认为自己正在启动多个应用程序。
然后在计时器上使用较细的颗粒。

 int main()
 {
     struct timeval time; 
     gettimeofday(&time,NULL);

     // microsecond has 1 000 000
     // Assuming you did not need quite that accuracy
     // Also do not assume the system clock has that accuracy.
     srand((time.tv_sec * 1000) + (time.tv_usec / 1000));

     // The trouble here is that the seed will repeat every
     // 24 days or so.

     // If you use 100 (rather than 1000) the seed repeats every 248 days.

     // Do not make the MISTAKE of using just the tv_usec
     // This will mean your seed repeats every second.
 }

21
实际上,我正在第二次启动我的应用程序的多个实例:)
Gary Richardson

2
我看到某个地方的解决方案适用于timeval / gettimeofday ....啊,是的,我自己的!您批评它非常糟糕,尽管重复的几率是1/1000000(使用您所做的相同假设)。而你的是1/60000。因此,这是我的解决方案,但更糟糕的是,它作为您的解决方案提供。
user39307

2
1)不要假设时钟分辨率为1/1000000。2)我的概率是1/2147483648(请注意tv_sec)。3)种子重复间隔为24天(应用之间的交互也很重要)。
马丁·约克

2
“重复的种子相隔24天”一秒钟内发生1000次碰撞。
user39307

2
除以tv_usec1000将丢弃所有熵。毫秒很容易预测,实际上很容易在不到一毫秒的时间内运行两次程序……
R .. GitHub停止帮助ICE

66

这是我用于可频繁运行(每秒多次)的小型命令行程序的方法:

unsigned long seed = mix(clock(), time(NULL), getpid());

混合在哪里:

// http://www.concentric.net/~Ttwang/tech/inthash.htm
unsigned long mix(unsigned long a, unsigned long b, unsigned long c)
{
    a=a-b;  a=a-c;  a=a^(c >> 13);
    b=b-c;  b=b-a;  b=b^(a << 8);
    c=c-a;  c=c-b;  c=c^(b >> 13);
    a=a-b;  a=a-c;  a=a^(c >> 12);
    b=b-c;  b=b-a;  b=b^(a << 16);
    c=c-a;  c=c-b;  c=c^(b >> 5);
    a=a-b;  a=a-c;  a=a^(c >> 3);
    b=b-c;  b=b-a;  b=b^(a << 10);
    c=c-a;  c=c-b;  c=c^(b >> 15);
    return c;
}

7
涉及pid的好主意。代码源链接已经过时,仍处于存档状态-该文本中名为“ Robert Jenkins的96位混合功能”的特定代码的源实际上也已链接:burtleburtle.net/bob/hash/doobs.html
Tobias Kienzler 2013年

15

如果您需要更好的随机数生成器,请不要使用libc rand。而是直接使用类似/dev/random/dev/urandom直接的内容(int直接从中读取内容或类似的内容)。

libc rand的唯一真正好处是给定了种子,这是可预见的,有助于调试。


1
在Windows上,您可以使用rand_s。
帕维尔Hajdan

17
您不应该/dev/urandom只为种子而从中重复读取随机数。它慢得多,并且耗尽了系统的熵池。
R .. GitHub停止帮助ICE,

1
只是在这里挑剔:给定相同的种子,每个PRNG都是可预测的。这就是P seudo在PRNG中的含义。
vanneto

@vanneto,当您弄清楚如何播种/dev/random使其完全可预测时,请告诉我:-P。它也是PRNG。
埃文·特兰

1
std::random_device/dev/urandomglibc中的便携式包装器:stackoverflow.com/a/13004555/895245
Ciro Santilli郝海东冠状病六四事件法轮功

13

在Windows上:

srand(GetTickCount());

提供的种子好time()于毫秒。


但是,您需要注意:“如果系统连续运行49.7天,时间将回零。为避免此问题,请使用GetTickCount64函数”。如果达到始终为零的程度,则会产生不良结果。
floele 2014年

10
@floele环绕零意味着它每隔49.7天从零开始重新计数,而不是在49.7天的正常运行时间后不再可用。
2014年

9

C ++ 11 random_device

如果您需要合理的质量,那么首先不要使用rand()。您应该使用该<random>库。它提供了许多出色的功能,例如用于不同质量/尺寸/性能折衷,重新进入和预定义分布的各种引擎,因此您最终不会出错。根据您的实现,它甚至可以轻松访问不确定的随机数据(例如/ dev / random)。

#include <random>
#include <iostream>

int main() {
    std::random_device r;
    std::seed_seq seed{r(), r(), r(), r(), r(), r(), r(), r()};
    std::mt19937 eng(seed);

    std::uniform_int_distribution<> dist{1,100};

    for (int i=0; i<50; ++i)
        std::cout << dist(eng) << '\n';
}

eng是随机性的来源,这里是梅森纳捻线器的内置实现。我们使用random_device对其进行播种,在任何体面的实现中,它将是非确定性的RNG,并使用seed_seq组合超过32位的随机数据。例如在libc ++中,random_device默认访问/ dev / urandom(尽管您可以给它另一个文件来访问)。

接下来,我们创建一个分布,以便在给定随机源的情况下,对该分布的重复调用将产生从1到100的整数均匀分布。然后,我们继续重复使用该分布并打印结果。


7

最好的方法是使用另一个伪随机数生成器。我建议使用Mersenne捻线机(和Wichmann-Hill)。

http://en.wikipedia.org/wiki/梅森_twister


我只是在建议这个发电机。我已经将它用于许多科学建模问题,并且比我所看到的替代方法产生了更好的结果。 math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html
Akusete,

3
如果您可以使用更好的RNG,为什么要使用它来播种标准RNG?只要使用更好的一个!
paxdiablo

这基本上是我的建议:-P
埃文·特兰

@Pax:使用另一个PRNG,而不使用PRNG作为种子!用更好的PRNG播种PRNG,谁会那样做?:D
user39307

13
这根本不能解决问题,您如何为MT PRNG播种?
Lie Ryan

7

我建议您在mozilla代码中看到unix_random.c文件。(猜它是mozilla / security / freebl / ...)应该在freebl库中。

那里它使用系统调用信息(例如pwd,netstat ....)为随机数生成噪声;它被编写为支持大多数平台(可以为我带来加分:D)。


6

您必须问自己的真正问题是您需要什么随机性质量。

libc random是一个LCG

无论您提供什么投入,随机性的质量都会很低。

如果仅需要确保不同的实例具有不同的初始化,则可以混合使用进程ID(getpid),线程ID和计时器。将结果与xor混合。熵对于大多数应用来说应该足够了。

范例:

struct timeb tp;
ftime(&tp);   
srand(static_cast<unsigned int>(getpid()) ^ 
static_cast<unsigned int>(pthread_self()) ^ 
static_cast<unsigned int >(tp.millitm));

为了获得更好的随机质量,请使用/ dev / urandom。您可以使用boost :: thread和boost :: date_time使以上代码可移植。


3

c++11乔纳森·赖特(Jonathan Wright)投票最多的帖子的版本:

#include <ctime>
#include <random>
#include <thread>

...

const auto time_seed = static_cast<size_t>(std::time(0));
const auto clock_seed = static_cast<size_t>(std::clock());
const size_t pid_seed =
      std::hash<std::thread::id>()(std::this_thread::get_id());

std::seed_seq seed_value { time_seed, clock_seed, pid_seed };

...
// E.g seeding an engine with the above seed.
std::mt19937 gen;
gen.seed(seed_value);

2
#include <stdio.h>
#include <sys/time.h>
main()
{
     struct timeval tv;
     gettimeofday(&tv,NULL);
     printf("%d\n",  tv.tv_usec);
     return 0;
}

tv.tv_usec以微秒为单位。这应该是可以接受的种子。


6
这是一个非常糟糕的想法。种子每秒重复一次。如果您多次启动应用程序,则很有可能会获得相同的种子。
马丁·约克

很坏?1/1000000(是的,这是百万分之一)重复的机会?如果您觉得幸运,那就去买彩票吧!9.5367431640625e-07(不到一秒)用python测量:从时间导入时间abs(time()-time())#在Windows上使用clock()-clock()
user39307 08-11-28

4
如果您只购买一张彩票,那就太好了。但是,如果您每秒购买1张彩票,那么您多久受到一次打击。可能性表示每年点击36次。我的方法1/2147483648的机率每24天重复一次,在1年中,即一年的1/143165576点击率!
马丁·约克

1
我使用tv_sec:基于纪元。每60年重复一次。将其乘以1000(我乘以1000来敲击高位)。这将为您提供一个每24天重复一次的值。然后,我在底部插入uSec。因此,我的时间精确到毫秒,每24天重复一次。
马丁·约克

1
这个讨论很好地说明了为什么rand(如密码学)很难正确设置,以及为什么人们应该使用boost :: rand而不是尝试自己滚动。除非您是该主题的专家,否则伪随机数会变得比随机数少,这有很多陷阱。
马丁·约克

2

只要您的程序仅在Linux上运行(并且您的程序是ELF可执行文件),就可以确保内核为您的进程提供ELF aux向量中的唯一随机种子。内核为您提供16个随机字节,每个字节各不相同,您可以使用获得getauxval(AT_RANDOM)。要将它们用于srand,请仅使用int它们中的一个,例如:

#include <sys/auxv.h>

void initrand(void)
{
    unsigned int *seed;

    seed = (unsigned int *)getauxval(AT_RANDOM);
    srand(*seed);
}

这也有可能转换为其他基于ELF的系统。我不确定在Linux以外的系统上实现了哪些aux值。


0

假设您有一个签名如下的函数:

int foo(char *p);

随机种子的最佳熵源是以下各项的哈希:

  • 完整的结果clock_gettime(秒和纳秒)而不会丢掉低位-它们是最有价值的。
  • 的值p,转换为uintptr_t
  • 的地址p,转换为uintptr_t

如果有的话,至少第三个(也可能是第二个)从系统的ASLR导出熵(初始堆栈地址以及当前堆栈地址在某种程度上是随机的)。

为了避免触及全局状态,我也将避免完全使用rand/ srand,因此您可以更好地控制所使用的PRNG。但是,无论您使用什么PRNG,上述过程都是一种无需过多工作即可获得不错的熵的好方法(并且相当便于移植)。


0

对于使用Visual Studio的用户,这是另一种方法:

#include "stdafx.h"
#include <time.h>
#include <windows.h> 

const __int64 DELTA_EPOCH_IN_MICROSECS= 11644473600000000;

struct timezone2 
{
  __int32  tz_minuteswest; /* minutes W of Greenwich */
  bool  tz_dsttime;     /* type of dst correction */
};

struct timeval2 {
__int32    tv_sec;         /* seconds */
__int32    tv_usec;        /* microseconds */
};

int gettimeofday(struct timeval2 *tv/*in*/, struct timezone2 *tz/*in*/)
{
  FILETIME ft;
  __int64 tmpres = 0;
  TIME_ZONE_INFORMATION tz_winapi;
  int rez = 0;

  ZeroMemory(&ft, sizeof(ft));
  ZeroMemory(&tz_winapi, sizeof(tz_winapi));

  GetSystemTimeAsFileTime(&ft);

  tmpres = ft.dwHighDateTime;
  tmpres <<= 32;
  tmpres |= ft.dwLowDateTime;

  /*converting file time to unix epoch*/
  tmpres /= 10;  /*convert into microseconds*/
  tmpres -= DELTA_EPOCH_IN_MICROSECS; 
  tv->tv_sec = (__int32)(tmpres * 0.000001);
  tv->tv_usec = (tmpres % 1000000);


  //_tzset(),don't work properly, so we use GetTimeZoneInformation
  rez = GetTimeZoneInformation(&tz_winapi);
  tz->tz_dsttime = (rez == 2) ? true : false;
  tz->tz_minuteswest = tz_winapi.Bias + ((rez == 2) ? tz_winapi.DaylightBias : 0);

  return 0;
}


int main(int argc, char** argv) {

  struct timeval2 tv;
  struct timezone2 tz;

  ZeroMemory(&tv, sizeof(tv));
  ZeroMemory(&tz, sizeof(tz));

  gettimeofday(&tv, &tz);

  unsigned long seed = tv.tv_sec ^ (tv.tv_usec << 12);

  srand(seed);

}

也许有点过分,但在快速间隔内效果很好。gettimeofday函数在这里找到。

编辑:经过进一步调查,rand_s可能是Visual Studio的一个很好的选择,它不仅是安全的rand(),而且完全不同,并且不使用srand的种子。我以为它和兰德几乎一样,只是“更安全”。

要使用rand_s,请不要忘记在包含stdlib.h之前#define _CRT_RAND_S。


在您的链接文章中,它/ 1000000UL用于gettimeofday。据我所知,您* 0.000001产生的结果很奇怪(负面)。这应该在您的代码中解决吗?
floele 2014年

它是从stackoverflow.com/a/5197874/990618复制而来的,看不到底片,错字,缺少行吗?
colin lamarre 2014年

不知道(尽管我很确定它不是错字),但我没有进一步研究它并rand_s最终使用了它。但是这篇文章social.msdn.microsoft.com/Forums/vstudio/en-US/…也使用了这种/ 1000000UL方法,对我来说似乎更安全。因此,如果有人正在使用此代码,则应在此行验证其是否正常运行。
floele 2014年

0

假设srand()+ rand()的随机性足以满足您的目的,诀窍是为srand选择最佳种子。time(NULL)是一个很好的起点,但是如果在同一秒内启动多个程序实例,则会遇到问题。添加pid(进程ID)是一项改进,因为不同的实例将获得不同的pid。我会将pid乘以一个因子,以将其扩展更多。

但是,假设您将其用于某些嵌入式设备,并且在同一网络中有多个设备。如果它们都立即通电,并且您在引导时自动启动程序的多个实例,则它们可能仍会获得相同的时间和pid,并且所有设备将生成相同的“随机”数字序列。在这种情况下,您可能需要为每个设备添加一些唯一标识符(例如CPU序列号)。

建议的初始化将是:

srand(time(NULL) + 1000 * getpid() + (uint) getCpuSerialNumber()); 

在Linux机器上(至少在我测试过的Raspberry Pi中),可以实现以下功能来获取CPU序列号:

// Gets the CPU Serial Number as a 64 bit unsigned int. Returns 0 if not found.
uint64_t getCpuSerialNumber() {

    FILE *f = fopen("/proc/cpuinfo", "r");
    if (!f) {
        return 0;
    }

    char line[256];
    uint64_t serial = 0;
    while (fgets(line, 256, f)) {
        if (strncmp(line, "Serial", 6) == 0) {
            serial = strtoull(strchr(line, ':') + 2, NULL, 16);
        }
    }
    fclose(f);

    return serial;
}

-2

在程序顶部包含标头,然后编写:

srand(time(NULL));

在程序中声明随机数之前。这是一个程序的示例,该程序输出一个介于1到10之间的随机数:

#include <iostream>
#include <iomanip>

using namespace std;

int main()
{
   //Initialize srand
   srand(time(NULL));

   //Create random number
   int n = rand() % 10 + 1;

   //Print the number
   cout << n << endl; //End the line

   //The main function is an int, so it must return a value
   return 0;
}

1
尽管在某些情况下可以接受time(NULL),但不建议使用这种策略来初始化srand()而不暴露缺点。例如,在许多进程在同一秒内被初始化的情况下,您的示例将在每个进程中产生相同的结果。此行为可能不是用户期望的。
Pedro Alves
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.