工作里遇到了关于随机数的问题:
使用相同的随机种子,换一个设备,还能得到相同的随机序列吗?
由于对随机数的生成理解不够深刻,还引发了一次跟同事之间的小讨论。不知为不知,在这里承认自己以前对随机数理解有限,把过程和经验记录于此。
随机数的基础用法和特性
编程过程中,随机数的基础用法和特性广为人知。但为了防止有对这个概念还不清楚的新手,还是先简单说明一下。
编程获取随机数的基本做法是:
- 在程序初始化1次随机种子。函数名通常是 srand(seed),这个seed的值通常是现在的时间戳
- 在需要随机数的时候,调用 rand() 函数,获取一个随机数
这样一来,在随机种子一旦被设定一次后(也就是调用了一次 srand(seed)之后) ,就会生成一个固定序列的一套随机数。(在同一台机器上,随机序列是相同的)
假设 seed = 0
在设置 srand(0) 后,连续调用 n 次rand(), 所生成的随机数序列,与下一次 srand(0) 后连续调用 n 次 rand() 所生成的随机数序列,是完全相同的。
因此,利用随机数的特性,通常一个应用程序只会初始化1次随机种子,之后每次需要一个随机数时,就调用 一次rand(), 就能生成一套随机的数列了。
为了保证每次开启应用程序种子都不同,很多时候,这1次初始化的随机种子的值,通常会设置为时间戳的值。
问题起因
工作中在开发一个三消类型的游戏,想增加一个类似“保存录像”的功能。拟定的做法是,在记录文件里,保存棋盘初始状态,每一次操作,以及随机种子。这样一来,录像文件里包含这些信息,就能够复原一局游戏的整个过程了。
其中,记录随机种子很关键。
这里打算利用 设置相同的随机种子,接下来所生成的随机数的序列会完全相同 的特性,来动态演算,从而实现录像的功能。
但是,问题是:
使用相同的随机种子,换一个设备,还能得到相同的随机序列吗?
讨论
在一些游戏开发的关键演算步骤中,我跟同事都有过这样的经验:客户端做一次计算,并把初始值、随机种子发送给服务器。服务器用这个初始值、随机种子,再做一次演算。这样能够有效防止客户端单方面作弊。
服务器和客户端肯定不会是同一台设备,甚至操作系统也很大概率不同。从这个应用场景上来看,相同的随机种子,在不同的设备上,会产生相同的随机数 这个结论似乎是正确的。
试验
为此,写一个简单的程序来验证。
#include <stdio.h>
#include <stdlib.h>
int main(int argc, const char * argv[])
{
srand(0);
for(int i = 0;i < 10;i++)
{
printf("%d\n",rand());
}
return 0;
}
这段代码在我的 win10 电脑上,和同事的 win7 电脑上跑,结果相同;
在我的 win10 电脑上,和在 macbook上跑,结果完全不同。
事实证明:
即使使用相同的随机种子,在不同设备上,仅靠计算机提供的 api 是无法得到相同的随机序列的
随机种子相同还不够
上面的试验证明,仅靠 C 语言的 srand, rand() ,是无法在种子相同,设备不同的情况下,获得相同的随机序列的。那么之前跟服务器通信时,只传递的随机种子,是怎么做到前后端同时验证的呢?
由此,很自然的就能想到:既然种子相同,结果却不同,肯定是由于调用的 rand() 函数的实现不同,导致不同平台的随机结果有区别的。
看来无法指望各个平台的 rand() 的实现,能够给出一套相同的结果。
既然系统有看不见的 rand() 的实现,那么只要自己写一个 rand() 的函数,能够实现根据固定的种子,给一套固定序列的返回值,那么就应该能实现“跨平台,跨机器 的 使用相同种子,得到相同随机数” 了。
既然需要自己重新实现一个 rand() ,就需要一个算法,来让每次返回的结果不同。
简单的伪随机算法
#include <stdlib.h>
static unsigned int RAND_SEED;
unsigned int random(void)
{
RAND_SEED=(RAND_SEED*123+59)%65536;
return(RAND_SEED);
}
void random_start(void)
{
int temp[2];
movedata(0x0040,0x006c,FP_SEG(temp),FP_OFF(temp),4);
RAND_SEED=temp[0];
}
main()
{
unsigned int i,n;
random_start();
for(i=0;i<10;i++)
printf("%u\t",random());
printf("\n");
}
上面的代码参考自百度百科 伪随机数
我们可以认为 调用 RAND_SEED = seed; 相当于调用了 c语言 srand(seed) 函数
之后再调用 random() 相当于调用了 c语言 rand() 函数
注意看 random() 函数的实现也很巧妙
static unsigned int RAND_SEED;
unsigned int random(void)
{
RAND_SEED=(RAND_SEED*123+59)%65536;
return(RAND_SEED);
}
用 %65536 来保证结果在 [0,65535] 区间内
RAND_SEED 是一个全局变量,在最初需要设置一个 seed 做种子。一旦种子设定后,每次 random() 调用,实际上就是根据种子生成一个值,并且把种子做改变。
生成值的时,可以随便的 乘以一个什么数 再加上个什么数,让结果显得很“随机”。
这样一来,就实现了一个简单的“伪随机算法”,基本能够达到应用的需求。
客户端、服务器,用相同的逻辑实现相同的随机函数之后,就能够保证无论在什么平台下,种子相同,生成的随机数序列也相同了。
事实上,后来和同事又跟了一下以前项目的前后端通信的代码,确实存在这么一个为随机数生成函数,并且函数的基本原理确实跟上面粘的百度百科里的例子是相同的。看来这样生成伪随机数的方法,是能够经受商业级产品的考验的。
结论
回到最开始的问题:
使用相同的随机种子,换一个设备,还能得到相同的随机序列吗?
答案就很明显了:
不行!随机种子一致、随机算法也得一致,才能保证在不同平台下,能够生成相同的随机数序列。
至于随机算法如何保证一致,肯定需要自己动手去编写(参照百度百科上的写法也很简单)。绝对不能依赖于各个语言的自带的 api 函数 (比如 c 语言的 rand() )。只靠随机种子+语言层面的随机数生成函数,是无法保证跨平台时,随机数序列的一致性的。