今天下午 pk 和我讨论了一个问题,他看到在另一个项目组的 lua 代码里有一段使用线性同余产生随机数的代码,但是那个项目组的同事告诉他这个函数生成的随机数是分布不均的。于是他想到了我前两天给他讲的关于 lua 里 % 这个取余数的符号跟 c 语言里的差别。由此展开了讨论。

首先铺陈背景,线性求余生成随机数的方式很普遍,对随机数要求不高的代码都可以使用这种方式,因为它实现简单,如果不知道原理的可以去 goole 一下。关于它所产生的随机数是否在域上分布均匀,暂且不论,权当均匀的吧。

我把代码做了简化,只留下最核心的几行,我们来看一下,这个产生伪随机数的算法是这样的:local IA = 3877

local IB = 29573

local g_seed = 42

function setseed(seed)

g_seed = seed

end

function getrandom(max)

max = max or 100

g_seed = g_seed * IA + IB

print(g_seed)

return g_seed % max

end

还算是简单的线性同余,当然各种边界条件的判断我都省略了。

这段代码的问题在哪里呢?首先有个前提条件,就是在实际我们求随机数的时候,不会去求一个负数域的随机数,换句话说,调用 getrandom() 函数传递的 max 值肯定是大于 0 的。

线性求余的公式倒是很简单:y = kx + b。这里我们的 k = IA = 3877,b = IB = 29573。为什么是这两个数呢,自己 google 吧。

这种方法求随机数,其实就是根据一个 x 值,来计算出 y 值,然后用 y 值模取随机数范围,并把这个 y 值作为下一次调用时的 x 值。举个例子,比如这里最初的 x 值就是那个 g_seed = 42,为什么是 42 也自己 google 吧。然后我要求一个 100 内的随机数,我调用一下 getrandom(100),计算过程如下:

g_seed = 42 * 3877 + 29573 = 192407

结果为 g_seed % 100 = 7

接着我又调用一次 getrandom(100) 想得到第二个随机数,计算过程就变成:

g_seed = 192407 * 3877 + 29573 = 745991512

结果为 g_seed % 100 = 12

当然我们肯定不会让 g_seed 超过一个 double 能表示的整数范围,额外处理这里就不谈了,这里说明的是工作的原理就是这样。

如果这个原理在 C/C++ 里面是不会有问题的,但是对于 lua 来说,% 号确实放这里不太合适,因为 lua 里 % 操作符等价于: a % b == a - math.floor( a / b ) * b

这样会有什么问题?由于我们一般不会 % 一个负数,因为求随机数范围一般是正数,但是 g_seed 却是可以由用户来设置的,所以我们无法保证 g_seed 始终为正数,根据上面这个等式,如果 a 为负数,b 为正数,那么 lua 里这个 % 的结果就肯定是一个正数。

这一点对我们的线性同余求随机数会造成什么问题呢?

问题就在于它使得随机的分布不在均匀,当然前提是我们假设之前的分布是均匀的。它使得当 g_seed 为负数时,原本应该得到负数随机数结果的那部分值变为了正数,导致随机数分布向一方倾斜。

我们先来看线性同余的一些性质,令最初的 x 为 x[0]:

我们发现线性同余求得的随机数分别是:

x[1] = kx[0] + b

x[2] = k^2x[0] + kb +b

……

r[1] = (kx[0] + b) % max

r[2] = (k^2x[0] + kb +b) % max

r[3] = (k^3x[0] + k^2b + kb + b) % max

……

我们来考虑 x[n], x[n] = k^nx[0] + b(k^n-1)/(k-1)

因为我们来看函数 y = 3877x + 29573 的函数图形:

可以看到,如果 x[0] 取一个正数,那么下一个 x[1] 会是一个更大的正数,之后的所有 x 都会更大;如果 x[0] 取一个比较大的负数的时候,之后的 x 都会是更大的负数,那么必然存在一个转折点,x[0] 取这个值的时候,之后的所有 x 都等于 x[0] ,这也就是这个函数的不动点,对于我们这段代码来说,令 3877x + 29573 = x,可以求出 x = - 29573 / 3876,如果 g_seed 最开始取了这个不动点的值,那么这个求随机数的算法就废了,因为每次的随机数都是一样的。

我其实想知道的是,最开始 g_seed 需要取一个怎么样的数,能够保证之后的所有 g_seed 都是单调增加的。当然只有当 g_seed 的取值范围存在负数的时候,我们的随机结果才是分布不均匀的。对这段代码来说,想要分布均匀,只需要最初的 g_seed 为非负数就不会有问题。

我们来寻找更普遍的性质,如果不改变这段代码的写法,依然使用 lua 的这个 % 操作符的含义(a % b == a - math.floor( a / b ) * b)来线性求余数产生伪随机数时,如果要保证随机数分布均匀,那么必须保证 x[n] 恒为非负数(n>=1)。

也就是 x[n] = k^nx[0] + b(k^n-1)/(k-1) 要恒大于等于 0(n>=1) 。k,b,x[0] 均为常数,我们整理得到:

x[n] = (x[0] + b/(k-1))k^n - b/(k-1)

对它求导得到:

x'[n] = (x[0] + b/(k-1))k^nlnk

我们想知道当 b 和 k 确定时(k~=0), x[0] 取什么数时,(1)x[1] >= 0 且 (2)x'[n] > 0。

(1) =》x[0] >= -b/k

(1)带入(2)得到:

并且我们还发现一点,就是当 k  > 1 的时候,x[n] 的绝对值随着 n 的增大是越来越大的,当 0 < k < 1 的时候, x[n] 的绝对值是收敛到 b 的绝对值的。

应用这个表我们最后知道,对于这段代码的 k,b 来说,x[0] 也就是 g_seed 最小不能小于 -29573 × 3878 / 3877^2,差不多是 -7.6 左右,只要最初的 g_seed 比这个值大,那么这段代码求出来的随机数就可以看作是没有问题的。

至此终于知道这段代码风险在哪了,不过既然知道了,就动手改一下呗,可代码是别人的动不得,囧。最近我要离职了,准备找一个新的环境,不知道朋友们有没有觉得靠谱的地,向我推荐下呗:)

线性同余法产生随机数C语言,线性同余生成随机数的一点思考相关推荐

  1. R语言使用random包生成随机数或者随机字符串实战:randomNumbers函数创建随机整数的数据集(包含重复项)、randomSequence函数创建不含重复项的随机序列数据集、创建随机字符串

    R语言使用random包生成随机数或者随机字符串实战:randomNumbers函数创建随机整数的数据集(包含重复项).randomSequence函数创建不含重复项的随机序列数据集.创建随机字符串 ...

  2. R语言:逆变换法生成随机数

    逆变换法生成随机数: 一.概念解释 1.PDF 2.PMF 3.CDF 二.连续型情况举例 三.离散型情况举例 一.概念解释 1.PDF probability density function 概率 ...

  3. R语言学习笔记:生成随机数

    R语言中,可以根据不同的分布生成随机数 均匀分布 runif(par1) runif(par1, min = par2, max = par3) 我们需要输入3个参数: par1:生成随机数的个数 p ...

  4. css 加随机数 引用_在CSS中生成随机数

    Python部落(python.freelycode.com)组织翻译,禁止转载,欢迎转发. Robin Rendle 于2017年1月11日 前几天,我遇到了一个特别有趣的问题.我想用random ...

  5. python自定义随机数_python:numpy.random模块生成随机数

    简介 所谓生成随机数,即按照某种概率分布,从给定的区间内随机选取一个数.常用的分布有:均匀分布(uniform distribution),正态分布(normal distribution),泊松分布 ...

  6. python生成50个随机数_Python内置random模块生成随机数的方法

    本文我们详细地介绍下两个模块关于生成随机序列的其他使用方法. 随机数参与的应用场景大家一定不会陌生,比如密码加盐时会在原密码上关联一串随机数,蒙特卡洛算法会通过随机数采样等等.Python内置的ran ...

  7. python中产生随机数模块_Python中random模块生成随机数详解

    Python中的random模块用于生成随机数.下面介绍一下random模块中最常用的几个函数. random.random random.random()用于生成一个0到1的随机符点数: 0 < ...

  8. java 生成随机数_不会生成随机数?我们来看看java生成随机数的10种方法

    1.引言 本文将探讨用 Java 生成随机数的不同方法. 2.Java API Java API 提供了几种随机数生成方法.让我们一起来了解一下. 2.1.java.lang.Math Math 类中 ...

  9. c语言产生服从正态分布的随机数,C语言产生满足正态分布的随机数

    C语言中可以通过rand函数生成满足均匀分布的随机数,但是生成满足正太分布的随机数就没有那么简单了,下面对常用的几种方法进行总结并用C++编程实现. 方法一:由均匀分布的随机数来产生 一个简单可行的并 ...

最新文章

  1. Oracle的控制文件
  2. 为什么需要批判性思维 -- 读《学会提问》
  3. Spring Boot Maven插件
  4. webpack从零开始第2课: 配置文件
  5. Spring教程--入门程序
  6. 单个字段去重并保留其他字段值
  7. [html] 说说你对HTML元素的显示优先级的理解
  8. c语言可以将负数强制转换成正数吗_C语言笔记(一、概述)
  9. 效果直逼flash的Div+Css+Js菜单
  10. 使用Axis,在webservice的服务器端如何取到客户端的IP地址
  11. 开课吧9.9元学python靠谱吗-quot;我,90 后,月薪 5k,副业 2w ”年轻人搞副业到底有多野?...
  12. 密码学:身份认证详解
  13. 使用第三方插件Curvy为unity场景快速生成运动轨迹与赛道
  14. 一次函数的斜率公式_一次函数斜率公式是什么?
  15. excel取消分页符
  16. 六万播放量的B站单细胞课程
  17. 【C/C++基础进阶系列】C/C++ STL -- 智能指针
  18. Flink Web UI不能访问
  19. Redis【有与无】【Lettuce】L4.Redis Sentinel
  20. 2019年Robomaster江苏省赛总结

热门文章

  1. 工业机器人技术试题_工业机器人技术题库及答案
  2. 【乐逍遥网站设计】带你了解2022年网站设计流行趋势
  3. Office总是无响应的解决办法
  4. Python 知识点超全学习笔记整理
  5. 1896-2021历届奥运会奖牌榜(Python数据处理)
  6. 纯js计算字符串中的字符的个数(汉字算两个字符计算)
  7. Windows命令行运行Java程序
  8. Windows无法连接SENS服务请求解决办法
  9. 华为eNSP配置PPPoE认证
  10. 批量合并格式不确定的Word文档