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)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM