广告广告
  加入我的最爱 设为首页 风格修改
首页 首尾
 手机版   订阅   地图  繁体 
您是第 83 个阅读者
 
发表文章 发表投票 回覆文章
  可列印版   加为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.389469 second(s),query:15 Gzip disabled
本站由 瀛睿律师事务所 担任常年法律顾问 | 免责声明 | 本网站已依台湾网站内容分级规定处理 | 连络我们 | 访客留言