廣告廣告
  加入我的最愛 設為首頁 風格修改
首頁 首尾
 手機版   訂閱   地圖  簡體 
您是第 95 個閱讀者
 
發表文章 發表投票 回覆文章
  可列印版   加為IE收藏   收藏主題   上一主題 | 下一主題   
冷場館女僕長 會員卡
個人頭像
個人文章 個人相簿 個人日記 個人地圖
特殊貢獻獎
頭銜:一位興趣使然的伺服主一位興趣使然的伺服主
特約版主
級別: 特約版主 該用戶目前不上站
版區: CS教學區
推文 x201 鮮花 x479
分享: 轉寄此文章 Facebook Plurk Twitter 複製連結到剪貼簿 轉換為繁體 轉換為簡體 載入圖片
推文 x0
[插件] random_R - 只用DEFINE寫成的隨機數公式INC  (快速、經典的LCG快速、經典的LCG)

圖 1. 經典的圖文不符   
經典的圖文不符



有猶豫這篇該放在插件區還是教學區...表情
但因為上篇Random教學放在教學區,那這篇也放在這好了表情
---------------------------------------------
基本上這篇的起源是來自AlliedModder一篇貼文,
裡面也是一些可用來代替原本native功能的define,
而裡面其中最吸引我的一條define是,可產生隨機數的define - random_R
使用的隨機公式則是經典的LCG公式:
複製程式
#define random_R(%0) (( randomSeed = (( 1103515245 * randomSeed  + 12345 ) & 0x7FFFFFFF )) % %0 ) 
公式看上去是不是很簡單?實際上真的很簡單表情
公式原理是用乘法和加法將數字計算到非常大再保留低位, 然後用 & 移除負數符號確保數字在 0 至 2147483647 內,
別看這公式很簡單,其實原理上這是random函數的簡化版,
也是ANSI C所使用的隨機數生成公式表情:
ANSI C LCG

而為何會另外寫一個INC來取代?用本身的random函數不好嗎?
只因一個字 :「快」,比內建的random函數快上約2倍(作者的測試數據):
複製程式
作者INC random_R( 50 ):
flTime: 0.000606
flTime: 0.000626
flTime: 0.000608

core.inc random( 50 ):
flTime: 0.001708
flTime: 0.001689
flTime: 0.001799 
而可以那麼快的原因,除了公式簡單外,
也因為它省略了stock回傳的時間,直接在插件內計算出隨機結果表情
老實说我也是因為這INC,所以我才開始留意random方面的事情,
不過這inc其實有好幾個問題:

首先既然是數學算式,那就需要一個數字才能進行計算,
而且這數字要有一定的變化,不然每次" 隨機结果 "的走向也會相同,
而這個數字我們會稱為" 種子(seed) ",
所以我們來看看作者INC中用什麼來當seed:
複製程式
public plugin_precache( )
{
       randomSeed = time( );
}
從上述代碼可以看到,INC會插件進行precache時,將randomSeed設定為 time() 的數值,
作者用time()來當seed有以下優點:
1.time函數的數值等同Unix Time,數字有一定長度,適合用來當seed
2.數值會隨時間而變化(每秒+1),確保每次獲取新seed值時不會相同

但他這樣寫法有一個問題,你不能直接include inc來使用....表情
因為plugin_precache()不能在同一插件放兩次或以上,
如果是在有plugin_precache()的插件include inc,編釋時會報错,
所以現在比較理想做法是, 當使用該define時,
自動分辨是否第一次執行,若是,使用time()數值來計算,
但這define要如何分辨seed是否已初始化?
我們來回顧一下LCG公式的構造:
複製程式
#define random_R(%0) (( randomSeed = (( 1103515245 * randomSeed  + 12345 ) & 0x7FFFFFFF )) % %0 ) 
& 0x7FFFFFFF 這點表示我們這公式不會出現負數,
既然負數沒可能出現,那就很適合用來當初始化開關~表情
所以我們可以把種子設為負數:
複製程式
stock randomSeed = -100;
然後再修改公式:
複製程式
#define random_R(%0) (( randomSeed = (( 1103515245 * ((randomSeed < 0) ? time() : randomSeed) + 12345 ) & 0x7FFFFFFF )) % %0 )
當種子數值低於0時,公式會自動採用time()的數值來生成隨機數
而當種子數值0或以上時,公式會採用上次計算的數值來生成隨機數
可能會有人擔心改動會影響本身公式運行的速度,我們直接來測試看看(各執行10000次):
複製程式
作者 INC random_R(100): 
0.00001495535600000
0.00001525460800000
0.00001556018000000

修改後 random_R(100): 
0.00001945871600000
0.00002005872000000
0.00001946002800000
在增加一個讀取time()數值+一個if判定之下,
雖影響一定效能但影響不大,而且使用INC時更方便了,利大於弊表情

解決了初始種子的問題了,那應該可以直接用吧?
嘛理論上可以但不建議,你問為什麼?
原因就是在於LCG產生的隨機數低位品質差,
低位品質差有什麼影響?我舉一個簡單的例子你就明白了,
假設你使用random_R(2),這時你就會發現問題了,
為何出現的結果也是0,1,0,1,0,1,0,1.....?

原因在於,%2只看你结果單數就给1,雙數就给0,
而再回望一下LCG公式:
複製程式
#define random_R(%0) (( randomSeed = (( 1103515245 * ((randomSeed < 0) ? time() : randomSeed) + 12345 ) & 0x7FFFFFFF )) % %0 )
當seed為單數 → 經單數乘法 = 單數 → 經單數加法 = 雙數,
反之如果seed為雙數 → 經單數乘法 = 雙數 → 經單數加法 = 單數,
所以結果會十分規律,單→雙→單→雙...變化只有2種,週期為2的「短週期」,
除此之外,LCG在其他低位也有着不同程度的「短週期」問題,不過沒單雙數那麼明顯,
所以我們再次看回Wiki:
ANSI C LCG
可以看到最後「Output Bits of seed (seed計算後輸出的bits)」,
這裡所寫的bit是什麼?
你若一個數字拆成二進制,例如「1753194833」作例子,你會看到:
複製程式
0110 1000 0111 1111 1010 0001 0101 0001
而上面所寫的bit是代表的是這些位置的0/1,
最右面開始為bit0,然後顺着左面數為bit1,bit2....如此類推,
所以我們要把LCG輸出的結果 >> 16 ,
>> 16 即是將目前的0/1 bit位置右移16位,
以剛才那组數字來做例子,即會變成:
複製程式
0000 0000 0000 0000 0110 1000 0111 1111
所以我我們來改INC了(順便把生成公式分開):
複製程式
/** 
 * Running ANSI C LCG PRNG.
 * 運行 ANSI C LCG 公式.
 */
#define LCG_Random (LCG_RandSeed = (1103515245 * ((LCG_RandSeed < 0) ? time() : LCG_RandSeed) + 12345) & RAND_MAX)

/** 
 * Returns a random integer number between 0 and (%0 - 1).
 * 抽取 0 至 %0 範圍內的整數
 */
#define random_R(%0) ((LCG_Random >> 16) % %0)

/**
 * Returns a random integer number between %0 and %1.
 * 抽取 %0 至 %1 範圍內的整數
 */
#define randomR_num(%0,%1) (%0 + ((LCG_Random >> 16) % (%1 - %0 + 1)))
那random_R就會只使用bit16位至bit30位來進行%運算了

而小數則可以不用右移,原因是小數運算的公式反而主要是看高位,
所以低位的問題不太影響,我們直接照之前的公式來生成小數:
複製程式
/**
 * Returns a uniform random float in the range [0, 1). 
 * 抽取 0 至 1 區間內的浮點數
 */
#define randomRf_unit (Float:(float(LCG_Random) * INV_MAX))

/** 
 * Returns a uniform random float in the range 0 and %0.
 * 抽取 0 至 %0 區間內的浮點數
 */
#define randomRf(%0) (Float:(randomRf_unit * Float:%0))

/** 
 * Returns a random float number between %0 and %1.
 * 抽取 %0 至 %1 區間內的浮點數
 */
#define randomR_float(%0,%1) (Float:(randomRf_unit * (Float:%1 - Float:%0) + Float:%0))

另外這裡還有一些應該實用的define:
複製程式
/** 
 * Reuturns 0 or 1 integer number
 * 隨機回傳 0 或 1
 */
#define randomR_bool (LCG_Random > (RAND_MAX >> 1))

/** 
 * Returns a random integer number between 0 and 255.
 * 抽取 0 至 255 範圍內的整數
 */
#define randomR_alphanum ((LCG_Random >> 16) & 0xFF)

/** 
 *  Check percentage chance , if more than %0 return 1 otherwise return 0, %0 = probability
 *       檢查機率百分比(整數),若抽中了會 %0 回傳 1,否則回傳 0 , %0 = 機率(整數)
 */
#define randomR_chance(%0) (%0 > ((LCG_Random >> 16) % 100))

/** 
 *       Check percentage chance , if more than %0 return 1 otherwise return 0 ,%0 = probability
 *       檢查機率百分比(小數),若抽中了會 %0 回傳 1,否則回傳 0 , %0 = 機率(小數)
 */
#define randomRf_chance(%0) ((Float:%0 * 0.01) > randomRf_unit)
-----------------------------------------
基本上就是這些,
有興趣也可以下載附件的random_R.inc用用看表情
在自己源碼加上#include <random_R>便可使用上面的define了~


本帖包含附件
zip random_R.zip   (2025-08-02 16:23 / 2 KB)  
說明: random_R.inc
下載次數:0


[ 此文章被冷場館女僕長在2025-08-03 13:06重新編輯 ]



我只是一個興趣使然的Server主.
獻花 x0 回到頂端 [樓 主] From:未知地址 | Posted:2025-08-02 16:23 |

首頁  發表文章 發表投票 回覆文章
Powered by PHPWind v1.3.6
Copyright © 2003-04 PHPWind
Processed in 0.039454 second(s),query:15 Gzip disabled
本站由 瀛睿律師事務所 擔任常年法律顧問 | 免責聲明 | 本網站已依台灣網站內容分級規定處理 | 連絡我們 | 訪客留言