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伪随机问题浅析的主要内容,如果未能解决你的问题,请参考以下文章