zoukankan      html  css  js  c++  java
  • 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)
  • 相关阅读:
    「字符串算法」第4章 字典树课堂过关
    「字符串算法」第3章 KMP 算法课堂过关
    「字符串算法」第2章 Hash 和 Hash 表课堂过关
    「基础算法」第5章 广度搜索课堂过关
    「基础算法」第3章 二分算法课堂过关
    「基础算法」第1章 递推算法强化训练
    「基础算法」第1章 递推算法课堂过关
    YbtOJ:冲刺 NOIP2020 模拟赛 Day10
    【模板】轻重链剖分
    LINUX-磁盘空间
  • 原文地址:https://www.cnblogs.com/SkyflyBird/p/9959990.html
Copyright © 2011-2022 走看看