[記錄點滴] 一個解決Lua 隨機數生成問題的辦法


[記錄點滴] 一個解決Lua 隨機數生成問題的辦法

0x00 摘要

本文是開發中的簡略記錄,具體涉及知識點有:Lua,隨機數。

0x01 背景

Lua語言生成隨機數需要用到兩個函數:

  • math.randomseed(n) : 用法是 接收一個整數n作為隨即序列的種子。
  • math.random([n [,m]]) : 用法有三種:
    • random(),產生[0, 1)之間的浮點隨機數。
    • random(n),產生[1, n]之間的整數。
    • .random(m, n),產生[n, m]之間的整數。

0x02 問題

2.1 Lua隨機數函數問題

Lua語言的隨機數函數存在問題:

  1. 第一個隨機數總是固定,而且常常是最小的那個值
  2. 如果 seed 很小或者seed 變化很小,產生的隨機序列仍然很相似。
  3. 如果很短的時間內多次運行這個程序,那么你得到的隨機序列會是幾乎不變的。

原因是LUA的random只是封裝了C的rand函數,使得random函數有一定的缺陷,

2.2 C語言隨機數函數問題

其實計算機產生的隨機數都是依照事先寫好的算法執行出來的,行為是可以預測的,所以計算機產生的隨機數都不是真正意義上的隨機數,只是偽隨機數,是以一個真值(也稱為種子)為初始條件,然后用一定的算法不停迭代產生隨機數。

C語言 rand的內部是用線性同余法做的,因為其周期特別長,所以在一定范圍內可以看成是隨機的。

線性同余方法(LCG)是一種產生偽隨機數的方法。它是根據遞歸公式實現:

RandSeed = (A * RandSeed + B) % M

線性同余法最重要的是定義了三個整數,乘數 A、增量 B和模數 M,其中A, B, M是產生器設定的常數。 LCG的周期最大為 M,但大部分情況都會少於M。要令LCG達到最大周期,應符合以下條件:

  • B,M互質;
  • M的所有質因數都能整除A-1;
  • 若M是4的倍數,A-1也是;
  • A,B,N[0]都比M小;
  • A,B是正整數。

0x03 解決方案

問題的解決方案就是:讓用戶使用randomseed先設一個隨機種子。比如在服務器啟動的時候設置一個隨機種子,讓系統產生的隨機序列不相同。

3.1 移位輪轉 + 線性同余

一種常見的辦法是以 time 函數返回的秒數為基准。但是因為如果需要短期內頻繁使用隨機數,這個方法不可行,因為容易產生類似數字,所以就把 time返回的數值字串倒過來(低位變高位), 再取高位幾位。這樣即使 time變化很小, 但是因為低位變了高位, 種子數值變化卻很大,就可以使偽隨機序列生成的更好一些。

這其實是一種 “移位輪轉“ 的思想

math.randomseed(tostring(os.time()):reverse():sub(1, 6))

3.2 Linux隨機種子

在linux下,我們可以使用 /dev/random以及/dev/urandom產生隨機種子。

其原理是利用當前系統的熵池來計算出一定數量的隨機比特,其中熵池是根據當前系統的“環境噪音”,它是由很多參數共同評估的,如內存的使用,文件使用量等等,環境噪音直接影響着所產生的隨機種子的有效性。

/dev/random與/dev/urandom之間存在區別:

  • urandom即”unlocked random”,,每次打開並讀取/dev/urandom時,會從熵池中隨機返回所需要的字節數。/dev/urandom的讀取操作不會阻塞,因為它會重復使用熵池中的數據以產生隨機數;
  • /dev/random則是每次讀之前去檢查熵池是否為空,若為空,則需要阻塞並去更新熵池。

對於我們來說,需要阻塞總是不好的,因此 urandom 更加理想

3.3 移位輪轉 + 線性同余 + Linux隨機種子

我們可以采用的是在之前辦法上,加入Linux隨機種子,代碼如下:

local _M = {}

function _M.random_seed()
    local in_file = io.open("/dev/urandom", "r")
    if in_file ~= nil then
    	  local d= in_file:read(4)
        math.randomseed(os.time() + d:byte(1) + (d:byte(2) * 256) + (d:byte(3) * 65536) + (d:byte(4) * 4294967296))
    else
        math.randomseed(tostring(os.time()):reverse():sub(1, 7))            
    end   
end

return _M

當然也可以再結合起來設置

math.randomseed(tostring(os.time()):reverse():sub(1, 6)+ d:byte(1) + (d:byte(2) * 256) + (d:byte(3) * 65536) + (d:byte(4) * 4294967296))

0xFF 參考

線性同余方法(LCG)產生隨機數

Linux下隨機數生成的函數與常見方法


免責聲明!

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



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