lua math.random伪随机问题浅析

本文探讨了 Lua 中使用 math.random 产生伪随机数的问题,解释了为何通过 os.time() 获取系统时间作为种子以避免伪随机数,并深入到 C 库源码层面分析了 rand 和 srand 的实现。建议为了获得更随机的结果,可以使用毫秒或微秒级别的系统时间作为种子。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在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/ 

如下是关于lua中math库的相关C库的方法:

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。 到此处我们关于随机数设定系统时间秒数为随机种子的原因到底为何,我们看下最后关于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; }

看到如上代码,我们应该明白,随机数的获取是根据next,也就是随机数种子经过计算得出,如果随机数种子没有设定的话,随机数种子默认为1。这样计算出来的数值就会十分集中。

因此,我们可以设定一个较小的随机种子进行尝试,如下:

-- 随机种子数值为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," "))

看到如上代码,为了避免伪随机,我们会使用很大的数值来避免,为了方便一般会使用os.time()。

 

但是如果你的程序在1秒内有多次操作,产生的随机数还会是伪随机。为了避免这种问题出现,前辈们会将随机种子的数值设置为毫秒级,甚至微秒级的数值来处理问题。或者

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

结论: 如果你对随机数值要求比较严格,建议设定一个很大的种子进行筛选。

转载于:https://www.cnblogs.com/SkyflyBird/p/9959990.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值