专栏首页重归混沌谈谈随机数的使用

谈谈随机数的使用

在日常开发中,伪随机函数几乎是必不可少的一个函数。

大部分我们在使用这个函数时,就自然而然拿来用了,很少去思考用的对不对,反正他是随机的,并且也很难去验证(需要各种大量数据统计)。

所以即使概率看起来不太对,也可以安慰自己说,其实是统计的数据量不够。但有时候真的是因为我们误用了随机函数。

在《计算机程序设计艺术》卷2中,详细介绍了线性同余序列的生成算法。

下面就以线性同余算法为例,来分析一下,为什么随机函数还有可能被误用,他原本不就是随机的么?

在游戏开发中,一般都会设计有开宝箱环节,假设每个宝箱每次开出A的概率是30%,开出B的概率是70%,宝箱可以重复开。

我们的代码可能会这么写

int open_box(box *b)

{

int n = rand() % 1000;

return n < 300 ? b->a : b->b;

}

是的, 这段代码就是开宝箱存在“垫刀”的根本原因。

我们来看一下线性同余(LCG)伪随机算法的定义:

Nj+1 = (A*Nj + B) (mod M)(j, j+1为下标)

其中A,B,M为线性同余序列生成常数。

LCG周期为M,A,B,M的关系限定如下:

1. B,M互质

2. M的所有质因子都能整除A-1

3. 若M是4的倍数,则A-1 也是

4. A, B, N0都比M小

5. A,B是正整数

通俗点来讲就是,线性同余生成的[0,M)个数在统计学意义上,是等概率出现的。也就是说在足够多次随机以后,他们出现的次数是相同的。

咋一看,感觉上面的代码好像没啥问题。因为[0,M)是等概率出现的,因此rand()%1000之后的值,也是等概率出现的。

但是!我们忽略了一个事实,这段代码意味着。所有人的所有宝箱(甚至还有其他系统)共用了一个伪随机序列。

假设rand()%1000的伪随机序列是这样的:

900,1,300, 500, 299, 785, 556 ...

我们来模拟一下多个宝箱交替打开的行为:

开宝箱1,rand()%1000返回的是900, 因此开出来的是B

开宝箱2,rand()%1000返回的是1, 因此开出来的是A

开宝箱1,rand()%1000返回的是300, 因此开出来的是B

开宝箱1,rand()%1000返回的是500, 因此开出来的是B

开宝箱2, rand()%1000返回的是299, 因此开出来的是A

如果宝箱1和宝箱2一直在以类似的顺序交替打开。即使开再多次,你也很难拍着胸脯说,宝箱1和宝箱2开出来的A,B概率分布是符合预期的。

毕竟你亲口告诉玩家,每个宝箱都有30%的概率开出来的是A,但是宝箱1却从来开不出A。

事情之所以会演变成这样。根本原因是,除了有一个伪随机序列之外,还有一个真随机事件,即玩家开宝箱的时机选择。

用软件工程的话来说,宝箱1和宝箱2通过一个全局变量(同一个线性同余序列)耦合在一起了,他们不是正交的。因此,开一个宝箱势必会影响另一个,所以它必然是错的。

还有很多类似的情况,比如一个技能的触发概率。我们本来告诉玩家的是每个技能以某种特定的概率触发,但是我们很可能做成了,以某种概率释放了某个技能。

在我们用随机函数之前,一定要先问问自己,所有使用rand()函数的地方其实是共用了同一个伪随机序列,这样真的没问题么?

本文分享自微信公众号 - 重归混沌(findstrx),作者:重归混沌

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-04-25

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 三角形光栅化时遇到的坑

    前一段时间打算写一个完整的游戏, 客户采用Unity3D引擎, 服务端则采用我自己的Silly网络框架。

    重归混沌
  • 再学计算机图形学入门

    在网上查资料时,无意间发现了一门课叫《现代计算机图形学入门》。于是事隔将近3年后,我再一次尝试图形学入门。这次学习从8月20号开始,一直到10月11日,约持续了...

    重归混沌
  • 再谈分布式服务架构

    在两年前,我曾经设计过一版,高可伸缩服务器架构, 但只进行了理论推演,并没有使用具体业务逻辑验证过。以这两年的经验来看,这个架构不具备可实施性。

    重归混沌
  • 10分钟完成一个最最简单的BLE蓝牙接收数据的DEMO

    这两天在研究蓝牙,网上有关蓝牙的内容非常有限,Github上的蓝牙框架也很少很复杂,为此我特地写了一个最最简单的DEMO,实现BLE蓝牙接收数据的问题,

    用户3112896
  • Oracle 18.3 : 透过告警日志从安装初始化过程看 18c 的新改变

    Oracle Database 18c 已经正式对外发布,第一个公共版本的版本号是 18.3 ,让我们从 18.3 的安装过程来一睹 18c 的改变。

    数据和云
  • 手机管家(Android)UI过度渲染自动化测试方案

    通常我们可以从各种渠道听到用户反馈app卡顿,究竟是什么用户觉得卡顿呢?

    腾讯移动品质中心TMQ
  • FFLIB C++ 异步&类型安全&printf风格的日志库

    摘要       C++程序的调试一般有调试器、printf、日志文件三种。Linux下的调试器为gdb,关于gdb的使用甚至可以单独用一本书来说明,但是本章并...

    知然
  • Android开发笔记(三十四)Excel文件的读写

    Android中操作Excel文件的场合较少见,主要是一些专业领域导入导出报表时使用,所以处理Excel读写的开源代码也很稀...

    用户4464237
  • Jmeter实例(一)_Beanshell脚本断言Mock接口

    我们在做接口断言的时候,如果遇到复杂的json,可以考虑用beanshell脚本去解析list,同时加入自定义的断言

    飞天小子
  • Array类型

    Array也是ECMAScript中常用类型之一,其特点是数组中的每一项都可以保存任何类型的数据,数组的大小可以动态调整。

    河岸飞流

扫码关注云+社区

领取腾讯云代金券