73

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

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

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

我正在考虑做一些 pid/unixtime 数学来获得一个 int,或者可能从/dev/urandom.

谢谢!

编辑

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

4

15 回答 15

71

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

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

混合在哪里:

// Robert Jenkins' 96 bit Mix Function
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;
}
于 2008-11-27T09:20:01.827 回答
63

最好的答案是<random>。如果您使用的是 C++11 之前的版本,您也可以查看 Boost random number stuff。

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

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

    ...
}

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


边注:

注意:下面的评论中讨论了这是不安全的(这是真的,但最终不相关(继续阅读))。因此,另一种选择是从随机设备/dev/random(或其他一些安全的实数(er)随机数生成器)中播种。但是:不要让这让你陷入一种虚假的安全感。这是rand()我们正在使用的。即使您使用出色生成的种子播种它,它仍然是可预测的(如果您有任何值,您可以预测下一个值的完整序列)。这仅对生成"pseudo"随机值有用。

如果您想要“安全”,您可能应该使用<random>(尽管我会在安全通知网站上做更多阅读)。请参阅下面的答案作为起点:https ://stackoverflow.com/a/29190957/14065以获得更好的答案。

次要注意:使用随机设备实际上比我下面的原始建议更好地解决了每秒启动多个副本的问题(只是不是安全问题)。


回到原来的故事:

每次启动时, 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.
 }
于 2008-11-27T05:30:14.160 回答
16

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

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

于 2008-11-27T04:54:18.413 回答
13

在窗户上:

srand(GetTickCount());

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

于 2008-11-27T08:34:04.197 回答
9

C++11random_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 播种它,在任何体面的实现中都将是一个非确定性的 RNG,并使用 seed_seq 组合超过 32 位的随机数据。例如在 libc++ random_device 默认访问 /dev/urandom (尽管您可以给它另一个文件来访问)。

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

于 2012-10-22T03:12:20.030 回答
7

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

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

于 2008-11-27T04:55:09.847 回答
7

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

它使用系统调用信息(如 pwd、netstat ....)为随机数生成噪声;它是为支持大多数平台而编写的(这可以获得我的奖励点:D)。

于 2008-11-27T06:07:32.667 回答
6

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

libc random 是一个LCG

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

如果您只是需要确保不同的实例具有不同的初始化,您可以混合使用进程 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 使上述代码可移植。

于 2008-11-27T13:24:01.490 回答
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);
于 2015-03-22T04:20:46.963 回答
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 以微秒为单位。这应该是可接受的种子。

于 2008-11-27T09:13:52.070 回答
2

只要您的程序仅在 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 以外的系统上实现了哪些辅助值。

于 2017-12-06T15:00:41.043 回答
0

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

int foo(char *p);

随机种子的一个很好的熵来源是以下哈希:

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

至少第三个,可能还有第二个,从系统的 ASLR 中获取熵,如果可用的话(初始堆栈地址,因此当前堆栈地址有些随机)。

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

于 2011-05-10T05:19:11.830 回答
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 几乎相同,只是“更安全”。

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

于 2013-01-16T04:57:16.373 回答
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;
}
于 2019-10-11T20:02:02.787 回答
-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;
}
于 2014-12-21T21:33:29.650 回答