在C++中种子随机数生成器

9

我有两个问题。

  1. 在C++中,除了使用srand(time(NULL)),还有哪些方法可以用来生成伪随机数?

  2. 我之所以问第一个问题是因为我目前正在使用时间作为我的生成器种子,但生成器返回的数字总是相同的。我很确定原因是因为存储时间的变量被截断了一定程度。(我收到了一个警告消息,“隐式转换失去整数精度:‘time_t’(又名‘long’)到‘unsigned int’”)。我猜测这告诉我本质上我的种子直到下一年才会改变。对于我的目的,使用时间作为我的种子完全可以工作,但我不知道如何摆脱这个警告。

我以前从未收到过这个错误消息,所以我认为它与我的Mac有关。它是64位OS X v10.8。我也使用Xcode编写和编译,但在其他电脑上使用Xcode时没有遇到任何问题。

编辑: 经过更多的尝试和研究,我发现64位的Mac存在一个bug。(如果我有误请纠正我。)如果你尝试让你的Mac使用time(NULL)作为种子选取1到7之间的随机数,你将总是得到数字四。一直都是。我最终使用mach_absolute_time()来给我的随机数生成器提供种子。显然,这消除了我的程序的所有可移植性……但我只是一个爱好者。

编辑2: 源代码:

#include <iostream>
#include <time.h>

using namespace std;

int main(int argc, const char * argv[]) {

srand(time(NULL));

cout << rand() % 7 + 1;

return 0;
}

我再次运行了这段代码来测试它。现在它只返回3。这一定与我的电脑有关,而不是与C++本身有关。


就像园艺一样,你只需要种下一颗种子。 - Greg Hewgill
我曾经遇到过类似的行为,发现初始数字是相同的,但如果我连续打印一系列随机数,它们会不同。通常第一个在每次运行时是相同的,但到第二或第三次进入打印循环时,它就会变得不同。尝试连续打印几个并验证每次运行它们是否总是相同的... - fooOnYou
@Redmastif:你错了。rand() 对我来说完美无缺。 - Martin York
@LokiAstari:可能是我的电脑的问题。 - user1116768
显示剩余2条评论
4个回答

13

简而言之,但很可能你做错了。你只需要设置一次种子,而你可能会写成这样:

for ( ... )
{
   srand(time(NULL));
   whatever = rand();
}

它应该是这样的

srand(time(NULL));
for ( ... )
{
   whatever = rand();
}

3
如果由于某些原因你无法轻易避免多次种子化,可以使用:srand(rand() ^ time(NULL));。如果重复执行这段代码也不会造成任何影响。 - David Schwartz
@DavidSchwartz,这可能是什么原因? - eq-
如果你有一个需要生成随机数的函数,而且在函数内部没有简单的方法可以判断随机数生成器是否已经被初始化。例如,所有调用你的代码可能都是你不维护的代码。 - David Schwartz
@eq-:这取决于库的目的。如果库的根本目的是提供这些结果,那当然可以。但是,如果调用者真的没有理由认为库需要种子化随机数生成器(RNG),那可能会是一个严重的问题。特别是如果你正在修改一个现有的库,即使RNG没有被种子化,它仍然可以正常工作。(尽管我认为有一个合理的观点,如果结果不需要随机性,为什么要费心呢?而且如果结果确实很重要,你应该使用更好的方法,而不是使用rand。) - David Schwartz
如果在类似循环中使用时间作为种子,存在的风险是,如果循环速度比time(NULL)函数返回的时间快,那么可能会多次使用相同的数字作为种子。 - kbickar
显示剩余2条评论

7

1.并非如此。您可以要求用户输入随机种子,或使用其他系统参数,但这不会有任何影响。

2.为了消除此警告,您需要进行显式转换。例如:

unsigned int time_ui = unsigned int( time(NULL) );
srand( time_ui );

或者

unsigned int time_ui = static_cast<unsigned int>( time(NULL) );

或者

unsigned int time_ui = static_cast<unsigned int>( time(NULL)%1000 );

为了检查这是否真的是转换问题,您可以简单地在屏幕上输出您的时间并自行查看。

std::cout << time(NULL);

我在屏幕上打印了 time(NULL),它正常工作并且每秒钟都会更新。然而,无论我如何调整,你的所有实现都对我无效。但是至少你让我有了一个起点,谢谢。 - user1116768
你有尝试在将time_ui值传递给srand()函数之前检查它吗? - klm123
是的。从行为上看,这似乎是种子由种子中的数字位数而不是数字本身决定的。我这么说是因为当我使用模运算时,随机数会改变。 - user1116768
http://www.cplusplus.com/reference/clibrary/cstdlib/srand/ 的示例对你有用吗? - klm123
不好意思,我刚刚写了一个小程序来输出随机数。使用 time(NULL) 种子,这个程序可以正常工作。所以我的原始源代码和获取随机数的方式肯定有其他问题。抱歉让大家为这个无意义的问题烦恼了。哈哈。 - user1116768

5
你的程序开头应该看到 random 这个词:
int main()
{
    // When testing you probably want your code to be deterministic
    // Thus don't see random and you will get the same set of results each time
    // This will allow you to use unit tests on code that use rand().
    #if !defined(TESTING)
    srand(time(NULL));  // Never call again
    #endif

    // Your code here.

}

0

对于x86,可以直接调用CPU时间戳计数器rdtsc,而不是使用库函数TIME(NULL)。以下是 1)读取时间戳 2)在汇编中用于种子RAND的代码:

rdtsc
mov edi, eax
call    srand

对于C++,使用g++编译器可以完成以下工作。
asm("rdtsc\n"
    "mov edi, eax\n"
    "call   srand");

注意:但如果代码在虚拟机中运行,则可能不建议使用。


3
这段内联汇编不安全,它没有告诉编译器你正在破坏寄存器,并且通过进行函数调用,破坏了rsp下面的红区。如果在你的程序中它偶然起作用,那么就是1)运气好,2)可能是因为你关闭了优化,所以编译器根本没有保留任何寄存器。不要使用内联汇编来实现这个功能。请使用immintrin.h中的 [__int64 _rdtsc (void)]。 - Peter Cordes
即使内在的RTDSC可能仍然不适合用于种子生成。time函数返回自1970年1月1日午夜以来的秒数,而RTDSC返回自计算机启动以来经过的时间。假设您总是在启动时运行程序。可能(不太可能,但可能)您可以在两个不同的引导时刻调用RTDSC相同的经过时间,在这种情况下,这两个运行将产生相同的随机序列。使用time()永远不会发生这种情况。 - victimofleisure

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接