建议初始化srand的方法?

时间:2008-11-27 04:37:23

标签: c++ random srand

我需要一种'好'的方法来初始化C ++中的伪随机数生成器。我发现an article表示:

  

为了生成随机的   数字,srand通常是初始化的   一些独特的价值,像那些   与执行时间有关。对于   例如,返回的值   函数时间(在标题中声明   ctime)每秒都不同,其中   对大多数人来说都很有特色   随机需求。

Unixtime对我的应用程序来说不够独特。有什么更好的方法来初始化它?奖励点如果它是可移植的,但代码将主要在Linux主机上运行。

我正在考虑做一些pid / unixtime数学来获取int,或者可能从/dev/urandom读取数据。

谢谢!

修改

是的,我实际上是每秒多次启动我的应用程序而且我遇到了碰撞。

15 个答案:

答案 0 :(得分:62)

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

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;
}

答案 1 :(得分:51)

最好的答案是使用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.
 }

答案 2 :(得分:15)

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

libc rand的唯一真正好处是给定一个种子,它是可预测的,有助于调试。

答案 3 :(得分:8)

在Windows上:

srand(GetTickCount());

提供比time()更好的种子,因为它以毫秒为单位。

答案 4 :(得分:8)

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是随机性的来源,这里是mersenne twister的内置实现。我们使用random_device对其进行播种,在任何体面的实现中,random_device都是非威慑性的RNG,而seed_seq用于组合超过32位的随机数据。例如,在libc ++中,random_device默认访问/ dev / urandom(尽管你可以给它另一个文件来访问)。

接下来,我们创建一个分布,这样,给定一个随机源,重复调用分布将产生从1到100的整数均匀分布。然后我们重复使用分布并打印结果。

答案 5 :(得分:7)

最好的方法是使用另一个伪随机数生成器。 Mersenne twister(和Wichmann-Hill)是我的推荐。

http://en.wikipedia.org/wiki/Mersenne_twister

答案 6 :(得分:7)

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

它使用系统调用信息(如pwd,netstat ....)为随机数生成噪声;它被编写为支持大多数平台(可以获得奖励积分:D)。

答案 7 :(得分: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。

使上述代码可移植

答案 8 :(得分:3)

Jonathan Wright最高投票帖子的c++11版本:

#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);

答案 9 :(得分: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以微秒为单位。这应该是可接受的种子。

答案 10 :(得分:0)

假设您的函数具有以下签名:

int foo(char *p);

随机种子的优秀熵源是以下哈希:

  • clock_gettime(秒和纳秒)的完整结果而不丢弃低位 - 它们是最有价值的。
  • p的值,投放到uintptr_t
  • p的地址,投放到uintptr_t

至少第三个,也可能是第二个,从系统的ASLR中导出熵(如果可用)(初始堆栈地址,因此当前堆栈地址有点随机)。

我也会完全避免使用rand / srand,这两者都是为了不触及全局状态,因此您可以更好地控制使用的PRNG。但是无论你使用什么样的PRNG,上面的程序都是一种很好的(并且相当便携)获得一些体面的熵而无需大量工作的方法。

答案 11 :(得分: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函数here

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

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

答案 12 :(得分:0)

只要您的程序仅在Linux上运行(并且您的程序是ELF可执行文件),就可以保证内核为您的进程提供ELF辅助向量中的唯一随机种子。内核为您提供了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以外的系统上实现了什么辅助值。

答案 13 :(得分: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;
}

答案 14 :(得分:-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;
}