lua math.random伪随机问题浅析

Posted skyflybird

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了lua math.random伪随机问题浅析相关的知识,希望对你有一定的参考价值。

在lua中,如果我们需要随机数的时候,会使用到math.random,为了避免伪随机我们的一般编写方式如下:

-- 获取当前系统时间(秒)作为随机种子
math.randomseed(os.time())

-- 有三种方式:
-- 1. 不带参数调用时,获取的是[0,1)范围内的随机浮点数
-- 2. 带一个整型参数时,获取的是[1,n]范围内的随机整数
-- 3. 带两个整型参数m,n时,获取的是[m,n]范围内随机整数
-- 请注意Lua5.3以后,参数一定要为整数,否则会返回错误:bad argument #1 to ‘random‘ (number has no integer representation)
math.random(10, 30)

为何避免伪随机,我们为何要使用os.time()获取系统时间秒数作为种子呢?接下来我们将从lua进入c中一层层的random,randomseed的实现慢慢剥离出来。

 

lua C库相关文件的官方下载地址:http://www.lua.org/ftp/

下载成功后,其相关文件在src目录中,我们可以查看lmathlib.c文件

或者查看lua 源码相关,其地址为:http://www.lua.org/source/5.1/ 

static const luaL_Reg mathlib[] = {
  {"abs",   math_abs},
  {"acos",  math_acos},
  {"asin",  math_asin},
  {"atan",  math_atan},
  {"ceil",  math_ceil},
  {"cos",   math_cos},
  {"deg",   math_deg},
  {"exp",   math_exp},
  {"tointeger", math_toint},
  {"floor", math_floor},
  {"fmod",   math_fmod},
  {"ult",   math_ult},
  {"log",   math_log},
  {"max",   math_max},
  {"min",   math_min},
  {"modf",   math_modf},
  {"rad",   math_rad},
  {"random",     math_random},    // 关于math.random的调用方法:math_random
  {"randomseed", math_randomseed},  // 关于math.randomseed的调用方法:math_randomseed
  {"sin",   math_sin},
  {"sqrt",  math_sqrt},
  {"tan",   math_tan},
  {"type", math_type},
  /* placeholders */
  {"pi", NULL},
  {"huge", NULL},
  {"maxinteger", NULL},
  {"mininteger", NULL},
  {NULL, NULL}
};

接下来我们查看下关于math_random,math_randomseed的实现:

/*
** This function uses ‘double‘ (instead of ‘lua_Number‘) to ensure that
** all bits from ‘l_rand‘ can be represented, and that ‘RANDMAX + 1.0‘
** will keep full precision (ensuring that ‘r‘ is always less than 1.0.)
*/
static int math_random (lua_State *L) {
  lua_Integer low, up;
  double r = (double)l_rand() * (1.0 / ((double)L_RANDMAX + 1.0));
  switch (lua_gettop(L)) {  /* check number of arguments */
    case 0: {  /* no arguments */
      lua_pushnumber(L, (lua_Number)r);  /* Number between 0 and 1 */
      return 1;
    }
    case 1: {  /* only upper limit */
      low = 1;
      up = luaL_checkinteger(L, 1);
      break;
    }
    case 2: {  /* lower and upper limits */
      low = luaL_checkinteger(L, 1);
      up = luaL_checkinteger(L, 2);
      break;
    }
    default: return luaL_error(L, "wrong number of arguments");
  }
  /* random integer in the interval [low, up] */
  luaL_argcheck(L, low <= up, 1, "interval is empty");
  luaL_argcheck(L, low >= 0 || up <= LUA_MAXINTEGER + low, 1,
                   "interval too large");
  r *= (double)(up - low) + 1.0;
  lua_pushinteger(L, (lua_Integer)r + low);
  return 1;
}

static int math_randomseed (lua_State *L) {
  l_srand((unsigned int)(lua_Integer)luaL_checknumber(L, 1));
  (void)l_rand(); /* discard first value to avoid undesirable correlations */
  return 0;
}

关于l_rand, l_srand的实现参考代码:

#if !defined(l_rand
#if defined(LUA_USE_POSIX)
#define l_rand()    random()
#define l_srand(x)    srandom(x)
#define L_RANDMAX    2147483647    /* (2^31 - 1), following POSIX */
#else
// windows平台等
#define l_rand()    rand()
#define l_srand(x)    srand(x)
#define L_RANDMAX    RAND_MAX         // 0x7fff
#endif
#endif

到此处,我们应该就明了,lua随机数的实现,来自于stdlib.h文件下的rand,srand. 那实现的方法如下:

// 参考来自:The Standard C Library 中第337页
static
unsigned long int next = 1; /* RAND_MAX assumed to be 32767 */ int rand(void) { next = next * 1103515245 + 12345; return((unsigned)(next/65536) % 32768); } void srand(unsigned seed) { next = seed; }

看到如上代码,我们应该明白,在没有设定randomseed(即srand)的情况下,我们的随机种子next默认为1,随机种子数值越小越容易导致随机数计算得出的数值集中,比如在lua中我们编写这样的程序:

-- 随机种子数值为100,每次执行的结果都是:1 1 1 3 4 2 4 1 4 1
math.randomseed(100)
-- 随机种子数字为os.time(),每次执行结果会发生改变
math.randomseed(os.time())
local randNum = {}
for i = 1,10 do
    table.insert(randNum,math.random(1,5))
end
print(table.concat(randNum," "))

到此刻,通过获取系统时间的秒值来设定随机种子,似乎伪随机的问题已经解决了。但是如果你的程序在1秒内有多次操作,产生的随机数还会是伪随机。

为了避免这种问题出现,前辈们会将随机种子的数值设置为毫秒级,甚至微秒级的数值来处理问题。或者

-- 将os.time()获取的系统秒数数值翻转(低位变高位),再取高6位,这样即使time变化很小
-- 由于低位变高位,数值就会变化很大,这样1秒内进行多次运行的话,效果会好些
local next = tostring(os.time()):reverse():sub(1, 6)
math.randomseed(next )

 


以上是关于lua math.random伪随机问题浅析的主要内容,如果未能解决你的问题,请参考以下文章

lua math

Lua 随机数生成问题

用lua生成一组10 个 1~100 之间不重复随机数。

[记录点滴] 一个解决Lua 随机数生成问题的办法

JavaScript中Math.random()方法产生的随机数包括0和1吗?

js 获取随机数