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)
  • 相关阅读:
    HDU 4069 Squiggly Sudoku
    SPOJ 1771 Yet Another NQueen Problem
    POJ 3469 Dual Core CPU
    CF 118E Bertown roads
    URAL 1664 Pipeline Transportation
    POJ 3076 Sudoku
    UVA 10330 Power Transmission
    HDU 1426 Sudoku Killer
    POJ 3074 Sudoku
    HDU 3315 My Brute
  • 原文地址:https://www.cnblogs.com/SkyflyBird/p/9959990.html
Copyright © 2011-2022 走看看