lua math.random偽隨機問題淺析
阿新 • • 發佈:2018-11-14
targe ensure table keep 當前系統時間 get eve ons sig
在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/
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. 那實現的方法如下:
// 參考來自: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; }
看到如上代碼,我們應該明白,在沒有設定randomseed(即srand)的情況下,我們的隨機種子next默認為1,隨機種子數值越小越容易導致隨機數計算得出的數值集中,比如在lua中我們編寫這樣的程序:
-- 隨機種子數值為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," "))
到此刻,通過獲取系統時間的秒值來設定隨機種子,似乎偽隨機的問題已經解決了。但是如果你的程序在1秒內有多次操作,產生的隨機數還會是偽隨機。
為了避免這種問題出現,前輩們會將隨機種子的數值設置為毫秒級,甚至微秒級的數值來處理問題。或者
-- 將os.time()獲取的系統秒數數值翻轉(低位變高位),再取高6位,這樣即使time變化很小 -- 由於低位變高位,數值就會變化很大,這樣1秒內進行多次運行的話,效果會好些 local next = tostring(os.time()):reverse():sub(1, 6) math.randomseed(next )
lua math.random偽隨機問題淺析