Lua 随机数问题深入理解

(基于Lua 5.1.4,在后续版本这个问题已经被修改)
游戏中经常会随机一些东西,当然少不了用随机数,今天用Lua 生成随机数的过程遇到一些小问题,整理一下:
先说几个相关函数:

math.randomseed()--初始化随机数种子
math.random()--随机0,1之间的数
math.random(n)--随机1,n的数
math.random(m,n)--随机m,n之间的数

问题大概是这样,本打算随机从一个table里选出一个数,于是就大概这么写了一下
 

local tab = {13,24,35,44,52,61}
function Random(tab)
    local maxsize = table.getn(tab)
    math.randomseed(os.time())
    local index = math.random(maxsize)
    return tab[index]
end

事实上生成的随机数远没有那么随机,经常出现13,也就是index一直随机到1,
第一个问题:我上面代码直接math.random(maxsize)总是随机出1,
第二个问题:于是先自己尝试在控制台使用

>math.randomseed(os.time())
>=math.random(),math.random(),math.random(),math.random(),math.random(),


连续尝试了十余次,这么多次随机出的数,第一个数总是相差特别小,
觉得可能有什么特别问题,于是认真查了下lua的资料,
如果math.randomseed(n),给出的n是个固定值,那么后面随机出的数也是一定的,这和其他很多语言的随机数(比如C的srand())类似,
http://lua-users.org/wiki/MathLibraryTutorial上的例子:

> math.randomseed(1234)
> = math.random(), math.random(), math.random()
0.12414929654836        0.0065004425183874      0.3894466994232
> math.randomseed(1234)
> = math.random(), math.random(), math.random()
0.12414929654836        0.0065004425183874      0.3894466994232


然后有这么一段话:
 A good* 'seed' is os.time(), but wait a second before calling the function to obtain another sequence! To get nice random numbers use:

    math.randomseed( os.time() )

If Lua could get milliseconds from os.time() the init could be better done. Another thing to be aware of is truncation of the seed provided. math.randomseed will call the underlying C function srand which takes an unsigned integer value. Lua will cast the value of the seed to this format. In case of an overflow the seed will actually become a bad seed, without warning [4] (note that Lua 5.1 actually casts to a signed int [5], which was corrected in 5.2).
(如果Lua可以从os.time()获得毫秒数,这个初始化可以做的更好(译者注:也就是说Lua只能从os.time()中取到"秒"这一级的数,而 取不到毫秒级,这就会导致时间变化很慢,每秒只改变种子最后一位,这个可以在控制台不断输出=os.time()试试).另一个需要注意的地方是你提供的 种子会被截断,math.randomseed将调用底层的C函数srand,这需要一个无符号整型值,Lua将把种子转化为这种形式.溢出的种子将变为 一个坏的种子,并且没有警告(Lua 5.1中会被转换成有符号整型,这已经在lua 5.2中被修正))
由于os.time()是秒级的,所以如果短时间内多次运行就会出现随机数相同的情况,math.random()还有一个问题就是当seed很小,或者两次seed变化很小时产生的随机序列很相似.
http://lua-users.org/wiki/MathLibraryTutorial提供了这样的一个生成随机数的方法:

-- improving the built-in pseudorandom generator
do
   local oldrandom = math.random
   local randomtable
   math.random = function ()
      if randomtable == nil then
         randomtable = {}
         for i = 1, 97 do
            randomtable[i] = oldrandom()
         end
      end
      local x = oldrandom()
      local i = 1 + math.floor(97*x)
      x, randomtable[i] = randomtable[i], x
      return x
   end
end


有了官方解释和解决办法,那么再仔细研究一下原因吧:

于是我又去查了下Lua 5.1.4源代码中lmathlib.c文件,看到如下函数:

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)) {  /* check number of arguments */
    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;
}


首先:
1.RAND_MAX是C中stdlib.h中宏定义的一个字符常量: #define RAND_MAX Ox7FFF
2.在标准的C库中函数rand()可以生成0~RAND_MAX之间的一个随机数
然后再说一下两个C函数:
rand     返回在0到RAND_MAX之间的伪随机数. 不接受参数作为随机数种子,因此产生的伪随机数列相同,有利于程序调试。
srand     初始化rand()接受无符号整型参数作为伪随机数种子.如果种子相同,伪随机数列也相同。

那么Lua取随机数的方式就是:

lua_Number r = (lua_Number)(rand()%RAND_MAX) / (lua_Number)RAND_MAX;


无参数时:
case 0: lua_pushnumber(L, r);
一个upper参数:
case 1: lua_pushnumber(L, floor(r*u)+1);
两个参数(lower,upper):
case 2:  lua_pushnumber(L, floor(r*(u-l+1))+l); 

这样第一个问题便可以解释了,我代码中

local index = math.random(maxsize)


index总是1的原因就是maxsize很小,而floor(maxsize*r)就会是经常为0,floor(maxsize*r)+1 为1也就可以解释了
第二个问题:
由于rand()每次产生同样的值,而随机数种子根据系统时间,并且只取到秒级,因此变化幅度很小,所以连续两次计算出来的随机数差别很小.
    所以,解决随机数问题有了另一个方法:让时间变得"快"一点,将os.time()取到的时间字符串翻转,这样两次的变化就比较大了
至此两个问题全部解决.
可以看出,公司的lua代码该更新了呀,换5.3吧!
参考:
Lua 5.1.4 源码
http://lua-users.org/wiki/MathLibraryTutorial
http://zh.wikipedia.org/wiki/Stdlib.h