了解Visual C++的 rand() 函数的算法

Understanding the algorithm of Visual C++'s rand() function

本文关键字:函数 算法 rand Visual C++ 了解      更新时间:2023-10-16

在C/c++中,当我们想要得到一个随机整数时,通常使用rand()srand()。但当我试图自己重写它时,我发现很难理解这个算法。这个函数只用几行就很容易写出来,但是这个公式却容易被误解。

主公式:

ptd->_holdrand = ptd->_holdrand * 214013L + 2531011L;

原始代码:

void __cdecl srand (unsigned int seed)
{
    _getptd()->_holdrand = (unsigned long)seed;
}
int __cdecl rand (void)
{
    _ptiddata ptd = _getptd();
    return ( ((ptd->_holdrand = ptd->_holdrand * 214013L + 2531011L) >> 16) & 0x7fff );
}

这只是模运算。您正在乘以并添加一个以2^32取模的数字(例如),并返回上面的16位作为您的"随机"数字。因为你是在对模数进行互质相乘和相加,这就产生了一种均匀分布的数。

仔细选择这两个数字是非常重要的。例如,如果您使用"* 4"answers"+ 8",您可能不会体验到很多随机性。

这个方案叫做线性同余。

该伪随机数生成器是一个线性同余生成器。

您可以在本月(7-2011)发表在Dr. Dobb's Journal (DDJ)上的一篇优秀文章中找到关于线性同余生成器(LCG)和其他类似家族或伪随机生成器的解释,以及关于这些特定常数的选择:快速,高质量,并行随机数生成器:比较实现。

我认为你需要在DDJ的网站(免费)注册才能阅读本文的第一部分(链接),但如果你对c++和数学感兴趣,你应该这样做…

在调用rand之前,由于srand的手册页指出"如果没有提供种子值,rand()函数将自动使用值1作为种子",那么调用rand的更好方法是首先调用srand,它将"将其参数设置为rand()返回的新伪随机整数序列的种子"。

作为一个例子,考虑以下awk, nawk, gawk代码,它在bash shell脚本中用于创建一个新的(随机)mac地址-即代码片段中引用的.genmacaddr:

enter code here
BEGIN {
     n0 = "00"
     srand()
     n1 = sprintf("%02x", int(255 * rand()))
     n2 = sprintf("%02x", int(255 * rand()))
     n3 = sprintf("%02x", int(255 * rand()))
     n4 = sprintf("%02x", int(255 * rand()))
     n5 = sprintf("%02x", int(255 * rand()))
     print n0":"n1":"n2":"n3":"n4":"n5
}

,其中bash shell脚本中的代码片段为:

enter code here
ifconfig eth0 down
newmacaddr=`nawk -f .genmacaddr -`
ifconfig eth0 hw ether $newmacaddr
ifconfig eth0 up

如果我没弄错的话,srand的种子值是从系统时钟派生出来的。

我希望这能帮助你理解一种可以工作的编码解决方案。

这是一个线性同余(如果你想深入了解这些伪随机值是如何产生的,你可以去查一下)

种子存储在_getptd()->_holdrand(以下称为holdrand)

这段代码通过执行普通的乘法和加法步骤来"工作",但随后溢出保持位得到隐含的"模量"0x100000000。

我提到这一点是因为它不是很明显,通常不被认为是好的风格。

从技术上讲,溢出整数变量会引起未定义行为,但在大多数平台上,这是可以预测的,所以微软的工程师忽略了这个问题。