lua math.random伪随机问题浅析
我们的一般编写随机如下:
-- 获取当前系统时间(秒)作为随机种子,有时也会使用毫秒:os.time() * 1000 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中的random,randomseed在C下的实现,参考资料:
lua源码下载:http://www.lua.org/ftp/
在线lua C库:http://www.lua.org/source/5.1/
Math库:http://lua-users.org/wiki/MathLibraryTutorial
我们可以摘录下lmathlib.c下关于random,randomseed C实现相关:
// 摘录:http://www.lua.org/source/5.1/lmathlib.c.html static const luaL_Reg mathlib[] = { {"random", math_random}, {"randomseed", math_randomseed}, {NULL, NULL} }; static int math_random (lua_State *L) { /* the `%' avoids the (rare) case of r==1, and is needed also because on some systems (SunOS!) `rand()' may return a value larger than RAND_MAX */ lua_Number r = (lua_Number)(rand()%RAND_MAX) / (lua_Number)RAND_MAX; switch (lua_gettop(L)) { /* 检测参数 */ case 0: { /* no arguments */ lua_pushnumber(L, r); /* Number between 0 and 1 */ break; } case 1: { /* only upper limit */ int u = luaL_checkint(L, 1); luaL_argcheck(L, 1<=u, 1, "interval is empty"); lua_pushnumber(L, floor(r*u)+1); /* int between 1 and `u' */ break; } case 2: { /* lower and upper limits */ int l = luaL_checkint(L, 1); int u = luaL_checkint(L, 2); luaL_argcheck(L, l<=u, 2, "interval is empty"); lua_pushnumber(L, floor(r*(u-l+1))+l); /* int between `l' and `u' */ break; } default: return luaL_error(L, "wrong number of arguments"); } return 1; } static int math_randomseed (lua_State *L) { srand(luaL_checkint(L, 1)); return 0; }
看到如上代码,我们可以了解:
1. 未调用srand,其next随机种子为1, 此时rand() 产生的随机数每次运行都会与上一次相同。
2. 若next随机种子设置的越大,伪随机的可能性就越小。比如:
-- 随机种子数值为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," "))
在cocos2d-lua下的function.lua中实现了新的随机数种子:
-- 摘录于: src/cocos/cocos2d/functions.lua -- 设置随机数种子 function math.newrandomseed() local ok, socket = pcall(function() return require("socket") end) if ok then // 关于*1000,在32位机器上,可能数值超过机器的最大值限定,而使得设置种子无效 math.randomseed(socket.gettime() * 1000) else math.randomseed(os.time()) -- 此处可修改为数值反转 end math.random() math.random() math.random() math.random() end
但是你的程序如果在1秒内有多次操作(密码锁),产生的随机数依然存在伪随机。因此我们可以这样:
-- 将os.time()获取的系统秒数数值翻转(低位变高位),再取高6位,这样即使time变化很小 -- 但是由于低位变高位,种子数值就会变化很大,这样1秒内进行多次运行的话,效果会好些 local next = tostring(os.time()):reverse():sub(1, 6) math.randomseed(next)