聊聊随机数

本文探讨了在不同设备上使用相同随机种子是否能得到相同随机序列的问题,通过实验发现仅靠标准API无法实现跨平台随机数一致。文章深入分析并提供了一个简单的伪随机算法,确保在任意平台上使用相同种子时,生成的随机数序列相同。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

工作里遇到了关于随机数的问题:

使用相同的随机种子,换一个设备,还能得到相同的随机序列吗?

由于对随机数的生成理解不够深刻,还引发了一次跟同事之间的小讨论。不知为不知,在这里承认自己以前对随机数理解有限,把过程和经验记录于此。

随机数的基础用法和特性

编程过程中,随机数的基础用法和特性广为人知。但为了防止有对这个概念还不清楚的新手,还是先简单说明一下。

编程获取随机数的基本做法是:

  1. 在程序初始化1次随机种子。函数名通常是 srand(seed),这个seed的值通常是现在的时间戳
  2. 在需要随机数的时候,调用 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() )。只靠随机种子+语言层面的随机数生成函数,是无法保证跨平台时,随机数序列的一致性的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值