當前位置:首頁 » 操作系統 » 偽隨機數生成演算法

偽隨機數生成演算法

發布時間: 2023-05-12 13:24:38

㈠ C++ 中的偽隨機數,例如rand(),是怎麼生成的

#include<math.h>
#define RAND_MAX 32767
unsigned int g_nSeed;//隨機數種子

void srand(unsigned int seed)//初始化隨機數種子
{
g_nSeed = seed;
}

int rand()
{
int a, c;
a = sqrt(RAND_MAX) + 1;
c = sqrt(3)*RAND_MAX;
g_nSeed = (g_nSeed*a+c)%RAND_MAX;//一種求隨機數種子的演算法,具體演算法指沒不一定是這樣,唯橡納可以有很多種,但大概原理就是這如孫樣
return g_nSeed;
}

㈡ 隨機數和偽隨機數的計算公式都是什麼呀

為追求真正的隨機序列,人們曾採用很多種原始的物理方法用於生成一定范圍內滿足精度(位數)的均勻分布序列,其缺點在於:速度慢、效率低、需佔用大量存儲空間且不可重現等。為滿足計算機模擬研究的需求,人們轉而研究用演算法生成模擬各種概率分布的偽隨機序列。偽隨機數是指用數學遞推公式所產生的隨機數。從實用的角度看,獲取這種數的最簡單和最自然的方法是利用計算機語言的函數庫提供的隨機數發生器。典型情況下,它會輸出一個均勻分布在0和1區間內的偽隨機變數的值。其中應用的最為廣泛、研究最徹底的一個演算法即線性同餘法。

線性同餘法LCG(Linear Congruence Generator)

選取足夠大的正整數M和任意自然數n0,a,b,由遞推公式:

ni+1=(af(ni)+b)mod M i=0,1,…,M-1

生成的數值序列稱為是同餘序列。當函數f(n)為線性函數時,即得到線性同餘序列:

ni+1=(a*ni+b)mod M i=0,1,…,M-1

以下是線性同餘法生成偽隨機數的偽代碼:

Random(n,m,seed,a,b)
{
r0 = seed;
for (i = 1;i<=n;i++)
ri = (a*ri-1 + b) mod m
}

其中種子參數seed可以任意選擇,常常將它設為計算機當前的日期或者時間;m是一個較大數,可以把它取為2w,w是計算機的字長;a可以是0.01w和0.99w之間的任何整數。

應用遞推公式產生均勻分布隨機數時,式中參數n0,a,b,M的選取十分重要。

例如,選取M=10,a=b =n0=7,生成的隨機序列為{6,9,0,7,6,9,……},周期為4。

取M=16,a=5,b =3,n0=7,生成的隨機序列為{6,1,8,11,10,5,12,15,14,9,0,3,2,13,4,7,6,1……},周期為16。

取M=8,a=5,b =1,n0=1,生成的隨機序列為{6,7,4,5,2,3,0,1,6,7……},周期為8。

Visual C++中偽隨機數生成機制

用VC產生隨機數有兩個函數,分別為rand(void)和srand(seed)。rand()產生的隨機整數是在0~RAND_MAX之間平均分布的,RAND_MAX是一個常量(定義為:#define RAND_MAX 0x7fff)。它是short型數據的最大值,如果要產生一個浮點型的隨機數,可以將rand()/1000.0,這樣就得到一個0~32.767之間平均分布的隨機浮點數。如果要使得范圍大一點,那麼可以通過產生幾個隨機數的線性組合來實現任意范圍內的平均分布的隨機數。

其用法是先調用srand函數,如

srand( (unsigned)time( NULL ) )

這樣可以使得每次產生的隨機數序列不同。如果計算偽隨機序列的初始數值(稱為種子)相同,則計算出來的偽隨機序列就是完全相同的。要解決這個問題,需要在每次產生隨機序列前,先指定不同的種子,這樣計算出來的隨機序列就不會完全相同了。以time函數值(即當前時間)作為種子數,因為兩次調用rand函數的時間通常是不同的,這樣就可以保證隨機性了。也可以使用srand函數來人為指定種子數分析以下兩個程序段,

程序段1:

//包含頭文件
void main() {
int count=0;
for (int i=0;i<10;i++){
srand((unsigned)time(NULL));
count++;
cout<<"No"<
//包含頭文件
void main() {
int count=0;
srand((unsigned)time(NULL));
for (int i=0;i<10;i++){
count++;
cout<<"No"<
No1=9694 No2=9694 No3=9694 No4=9694 No5=9694
No6=9694 No7=9694 No8=9694 No9=9694 No10=9694

程序段2的運行結果為:

No1=10351 No2=444 No3=11351 No4=3074 No5=21497
No6=30426 No7=6246 No8=24614 No9=22089 No10=21498

可以發現,以上兩個程序段由於隨機數生成時選擇的種子的不同,運行的結果也不一樣。rand()函數返回隨機數序列中的下一個數(實際上是一個偽隨機數序列,序列中的每一個數是由對其前面的數字進行復雜變換得到的)。為了模模擬正的隨機性,首先要調用srand()函數給序列設置一個種子。為了更好地滿足隨機性,使用了時間函數time(),以便取到一個隨時間變化的值,使每次運行rand()函數時從srand()函數所得到的種子值不相同。偽隨機數生成器將作為"種子"的數當作初始整數傳給函數。這粒種子會使這個球(生成偽隨機數)一直滾下去。

程序段1中由於將srand()函數放在循環體內,而程序執行的CPU時間較快,調用time函數獲取的時間精度卻較低(55ms),這樣循環體內每次產生隨機數用到的種子數都是一樣的,因此產生的隨機數也是一樣的。而程序段2中第1次產生的隨機數要用到隨機種子,以後的每次產生隨機數都是利用遞推關系得到的。 基於MFC的隨機校驗碼生成

Web應用程序中經常要利用到隨機校驗碼,校驗碼的主要作用是防止黑客利用工具軟體在線破譯用戶登錄密碼,校驗碼、用戶名、密碼三者配合組成了進入Web應用系統的鑰匙。在利用VC開發的基於客戶機/瀏覽器(Client/Server)模式的應用軟體系統中,為了防止非法用戶入侵系統,通常也要運用隨機校驗碼生成技術。

㈢ 詳解隨機數的生成

隨機數參與的應用場景大家一定不會陌生,比如密碼加鹽時會在原密碼上關聯一串隨機數,蒙特卡洛絕喊雀演算法會通過隨機數采樣等等。Python內置的random模塊提供了生成隨機數的方法,使用這些方法時需要導入random模塊。

下面介紹下Python內置的random模塊的幾種生並早成隨機數滲租的方法。

1、random.random()隨機生成 0 到 1 之間的浮點數[0.0, 1.0)。注意的是返回的隨機數可能會是 0 但不可能為 1,即左閉右開的區間。

2、random.randint(a , b)隨機生成 a 與 b 之間的整數[a, b],a<=n<=b,隨機整數不包含 b 時[a, b)可以使用 random.randrange() 方法。

3、random.randrange(start,stop,step)按步長step隨機在上下限范圍內取一個隨機數,start<=n<stop。

4、random.uniform(a, b)隨機生成 a 與 b 之間的浮點數[a, b],a<=n<=b。

5、random.choice()從列表中隨機取出一個元素,比如列表、元祖、字元串等。注意的是,該方法需要參數非空,否則會拋出 IndexError 的錯誤。

6、random.shuffle(items) 把列表 items 中的元素隨機打亂。注意的是,如果不想修改原來的列表,可以使用 模塊先拷貝一份原來的列表。

7、random.sample(items, n)從列表 items 中隨機取出 n 個元素。

Python 的random模塊產生的隨機數其實是偽隨機數,依賴於特殊演算法和指定不確定因素(種子seed)來實現。如randint方法生成一定范圍內的隨機數,會先指定一個特定的seed,將seed通過特定的隨機數產生演算法,得到一定范圍內隨機分布的隨機數。因此對於同一個seed值的輸入產生的隨機數會相同,省略參數則意味著使用當前系統時間秒數作為種子值,達到每次運行產生的隨機數都不一樣。

numpy庫也提供了random模塊,用於生成多維度數組形式的隨機數。使用時需要導入numpy庫。

下面介紹下numpy庫的random模塊的幾種生成隨機數的方法。

1、numpy.random.rand(d0,d1,…,dn)

2、numpy.random.randn(d0,d1,…,dn)

3、numpy.random.randint(low, high=None, size=None, dtype=』l』)

4、numpy.random.seed()

㈣ 密碼那些事

之前在工作中經常用密鑰,但是不知道其中的原因,現在閑下來就來看下,再看的過程發現這個隨機數概念很模糊,於是就查了下,現總結如下:

0x01 隨機數

概述

隨機數在計算機應用中使用的比較廣泛,最為熟知的便是在密碼學中的應用。本文主要是講解隨機數使用導致的一些Web安全風。

我們先簡單了解一下隨機數

分類

隨機數分為真隨機數和偽隨機數,我們程序使用的基本都是偽隨機數,其中偽隨機又分為強偽隨機數和弱偽隨機數。

真隨機數,通過物理實驗得出,比如擲錢幣、骰子、轉輪、使用電子元件的噪音、核裂變等

偽隨機數,通過一定演算法和種子得出。軟體實現的是偽隨機數

強偽隨機數,難以預測的隨機數

弱偽隨機數,易於預測的隨機數

特性

隨機數有3個特性,具體如下:

隨機性:不存在統計學偏差,是完全雜亂的數列

不可預測性:不能從過去的數列推測出下一個出現的數

不可重現性:除非將數列本身保存下來,否則不能重現相同的數列

隨機數的特性和隨機數的分類有一定的關系,比如,弱偽隨機數只需要滿足隨機性即可,而強位隨機數需要滿足隨機性和不可預測性,真隨機數則需要同時滿足3個特性。

引發安全問題的關鍵點在於不可預測性。

偽隨機數的生成

我們平常軟體和應用實現的都是偽隨機數,所以本文的重點也就是偽隨機數。

偽隨機數的生成實現一般是演算法+種子。

具體的偽隨機數生成器PRNG一般有:

線性同餘法

單向散列函數法

密碼法

ANSI X9.17

比較常用的一般是線性同餘法,比如我們熟知的C語言的rand庫和java的java.util.Random類,都採用了線性同餘法生成隨機數。

應用場景

隨機數的應用場景比較廣泛,以下是隨機數常見的應用場景:

驗證碼生成

抽獎活動

UUID生成

SessionID生成

Token生成

CSRF Token

找回密碼Token

游 戲 (隨機元素的生成)

洗牌

俄羅斯方塊出現特定形狀的序列

游戲爆裝備

密碼應用場景

生成密鑰:對稱密碼,消息認證

生成密鑰對:公鑰密碼,數字簽名

生成IV: 用於分組密碼的CBC,CFB和OFB模式

生成nonce: 用於防禦重放攻擊; 分組密碼的CTR模式

生成鹽:用於基於口令的密碼PBE等

0x02 隨機數的安全性

相比其他密碼技術,隨機數很少受到關注,但隨機數在密碼技術和計算機應用中是非常重要的,不正確的使用隨機數會導致一系列的安全問題。

隨機數的安全風險

隨機數導致的安全問題一般有兩種

應該使用隨機數,開發者並沒有使用隨機數;

應該使用強偽隨機數,開發者使用了弱偽隨機數。

第一種情況,簡單來講,就是我們需要一個隨機數,但是開發者沒有使用隨機數,而是指定了一個常量。當然,很多人會義憤填膺的說,sb才會不用隨機數。但是,請不要忽略我朝還是有很多的。主要有兩個場景:

開發者缺乏基礎常識不知道要用隨機數;

一些應用場景和框架,介面文檔不完善或者開發者沒有仔細閱讀等原因。

比如找回密碼的token,需要一個偽隨機數,很多業務直接根據用戶名生成token;

比如OAuth2.0中需要第三方傳遞一個state參數作為CSRF Token防止CSRF攻擊,很多開發者根本不使用這個參數,或者是傳入一個固定的值。由於認證方無法對這個值進行業務層面有效性的校驗,導致了 OAuth 的CSRF攻擊。

第二種情況,主要區別就在於偽隨機數的強弱了,大部分(所有?)語言的API文檔中的基礎庫(常用庫)中的random庫都是弱偽隨機,很多開發自然就直接使用。但是,最重要也最致命的是,弱偽隨機數是不能用於密碼技術的。

還是第一種情況中的找回密碼場景,關於token的生成, 很多開發使用了時間戳作為隨機數(md5(時間戳),md5(時間戳+用戶名)),但是由於時間戳是可以預測的,很容易就被猜解。不可預測性是區分弱偽隨機數和強偽隨機數的關鍵指標。

當然,除了以上兩種情況,還有一些比較特別的情況,通常情況下比較少見,但是也不排除:

種子的泄露,演算法很多時候是公開的,如果種子泄露了,相當於隨機數已經泄露了;

隨機數池不足。這個嚴格來說也屬於弱偽隨機數,因為隨機數池不足其實也導致了隨機數是可預測的,攻擊者可以直接暴力破解。

漏洞實例

wooyun上有很多漏洞,還蠻有意思的,都是和隨機數有關的。

1.應該使用隨機數而未使用隨機數

Oauth2.0的這個問題特別經典,除了wooyun實例列出來的,其實很多廠商都有這個問題。

Oauth2.0中state參數要求第三方應用的開發者傳入一個CSRF Token(隨機數),如果沒有傳入或者傳入的不是隨機數,會導致CSRF登陸任意帳號:

唯品會賬號相關漏洞可通過csrf登錄任意賬號

人人網 - 網路 OAuth 2.0 redirect_uir CSRF 漏洞

2.使用弱偽隨機數

1) 密碼取回

很多密碼找回的場景,會發 送給 用戶郵件一個url,中間包含一個token,這個token如果猜測,那麼就可以找回其他用戶的密碼。

1. Shopex  4.8.5密碼取回處新生成密碼可預測漏洞

直接使用了時間函數microtime()作為隨機數,然後獲取MD5的前6位。

1. substr(md5(print_r(microtime(),true)),0,6);

php 中microtime()的值除了當前 伺服器 的秒數外,還有微秒數,微妙數的變化范圍在0.000000 -- 0.999999 之間,一般來說,伺服器的時間可以通過HTTP返回頭的DATE欄位來獲取,因此我們只需要遍歷這1000000可能值即可。但我們要使用暴力破解的方式發起1000000次請求的話,網路請求數也會非常之大。可是shopex非常貼心的在生成密碼前再次將microtime() 輸出了一次:

1. $messenger = &$this->system->loadModel('system/messenger');echo microtime()."

";

2.奇虎360任意用戶密碼修改

直接是MD5( unix 時間戳)

3.塗鴉王國弱隨機數導致任意用戶劫持漏洞,附測試POC

關於找回密碼隨機數的問題強烈建議大家參考拓哥的11年的文章《利用系統時間可預測破解java隨機數| 空虛浪子心的靈魂》

2) 其他隨機數驗證場景

CmsEasy最新版暴力注入(加解密缺陷/繞過防注入)

弱偽隨機數被繞過

Espcms v5.6 暴力注入

Espcms中一處SQL注入漏洞的利用,利用時發現espcms對傳值有加密並且隨機key,但是這是一個隨機數池固定的弱偽隨機數,可以被攻擊者遍歷繞過

Destoon B2B  2014-05-21最新版繞過全局防禦暴力注入(官方Demo可重現)

使用了microtime()作為隨機數,可以被預測暴力破解

Android  4.4之前版本的Java加密架構(JCA)中使用的Apache Harmony 6.0M3及其之前版本的SecureRandom實現存在安全漏洞,具體位於classlib/moles/security/src/main/java/common/org/apache/harmony/security/provider/crypto/SHA1PRNG_SecureRandomImpl.java

類的engineNextBytes函數里,當用戶沒有提供用於產生隨機數的種子時,程序不能正確調整偏移量,導致PRNG生成隨機序列的過程可被預測。

Android SecureRandom漏洞詳解

安全建議

上面講的隨機數基礎和漏洞實例更偏重是給攻擊者一些思路,這里更多的是一些防禦和預防的建議。

業務場景需要使用隨機數,一定要使用隨機數,比如Token的生成;

隨機數要足夠長,避免暴力破解;

保證不同用處的隨機數使用不同的種子

對安全性要求高的隨機數(如密碼技術相關)禁止使用的弱偽隨機數:

不要使用時間函數作為隨機數(很多程序員喜歡用時間戳) Java:system.currenttimemillis() php:microtime()

不要使用弱偽隨機數生成器 Java: java.util.Random PHP: rand() 范圍很小,32767 PHP: mt_rand() 存在缺陷

強偽隨機數CSPRNG(安全可靠的偽隨機數生成器(Cryptographically Secure  Pseudo-Random Number Generator)的各種參考

6.強偽隨機數生成(不建議開發自己實現)

產生高強度的隨機數,有兩個重要的因素:種子和演算法。演算法是可以有很多的,通常如何選擇種子是非常關鍵的因素。 如Random,它的種子是System.currentTimeMillis(),所以它的隨機數都是可預測的, 是弱偽隨機數。

強偽隨機數的生成思路:收集計算機的各種,鍵盤輸入時間,內存使用狀態,硬碟空閑空間,IO延時,進程數量,線程數量等信息,CPU時鍾,來得到一個近似隨機的種子,主要是達到不可預測性。

暫時先寫到這里

㈤ 淺析C語言中的偽隨機數

哈嘍,大家好,我是北方素素~

首先給大家解釋一下這個標題,什麼叫做「淺析」呢?就是稍微分析一下,哈哈。用了「淺析」就突然感覺高大上了,其實並沒有多深奧,只是以鏈棗羨我的理解給大家解釋一下。

我們知道,在一些書籍中,使用C語言生成隨機數一般是這樣寫的:

srand(unsigned(time(NULL)));

int rand_number = rand();

這里主要涉及到了三個函數,srand(),rand()和time()。

不知道這三個函數是做什麼的?MSDN文檔中解釋了這三個函數的作用:

srand():Sets the starting seed value for the pseudorandom number generator(設置偽隨機數生成器的起始種子值)。

它的搭檔rand():Generates a pseudorandom number(生成一個偽隨機數)。

而time()的解釋就更簡單了:Get the system time(獲得系統時間)。

這三個函數組合在一起,就可以生成隨機數。

細心的同學可能會發現這里有一個特殊的詞-偽隨機數。那麼什麼是偽隨機數呢?

關於偽隨機數的概念我就不多說了,大家自行搜索一下就有很多解釋,在這里我只說一下自己的理解:

先說一下真隨機,真隨機也就是我們日常說的隨機,一個隨機事件的結果是不確定的,比如拋硬幣,在正常情況下,拋硬幣的結果是不確定的,換句話說,結果是不可預測的。

然後說說偽隨機,偽隨機是計算機生成隨機數的一種方式,計算機不能真正模仿隨機事件,而只能通過計算來生成隨機數。換句話說,如果我們知道了計算機計算隨機數的演算法,我們是可以預測偽隨機數的。

當然我們得用例子說話。

從上面的討論我們可以知道,因為rand()產生的數字是偽隨機數,所以它一定有一個固定的演算法來生成偽隨機數,那麼演算法是固定的,不固定的只有srand()所設置的種子了。

確定了這一點之後,我們就可以做實驗驗證我們的猜想了。

如果srand()設定的種子是一個固定值會發生什麼?

srand(1);

int rand_number = rand();

在我執行了N次之後,結果都是固定的。(大家可以親自試一試)

這就說明我們的「隨機數」就是通過某種演算法計算出來的結果,所以要想這個結果發生變化,就需要設定一個不斷變化的「種子」。

那麼這里也就解釋了「種子」的作用-用來給隨機數生成器提供一個輸入,之後隨機數生成器就會使用這個「種子」生成不同的偽隨機數。

舉個栗子~

如果我們的隨機數生成演算法是這樣的:

rand=1+x;

那麼我們的「種子」就是上式中的x,,每當我們輸入不同的x,產生的結果rand也就會不同。

至於為什麼選擇time()作為「種子」嘛,上面說了,這個函數呢,用來獲取系統時間,因為系統時間是一直在變化的,就相當於我們隨機數的「種子」是在一直變化的,所以每次調用time()函數,都會獲得不同的值,這樣我們的rand()每次生成的結果也就不一樣啦~

最後總結一下這三個函數在生成偽隨機數的時候是怎麼配合的。

首先,time()獲取了系統時間;然後,srand()把獲取到的系統時間設置為rand()的「種子」;最後,由rand()通過計算,把「種子」轉換為一個數字。

好啦,這篇文章就寫到這里了~

歡迎大家訪問我的網站:https://bfss.github.io/,如岩缺果覺得我的文章幫到了您,可以在網站上點擊「支持我」對我進行鼓勵喲~(手機端網頁在左上角棚拍,電腦端網頁在右上角)

https://www.bilibili.com/read/cv311534

出處: bilibili

㈥ 什麼是「隨機」教你分清「偽隨機」和「真隨機」

​很久以前流傳著這樣一則笑話:一個身患重病的人決定去動手術。在手術之前,他問醫生:「這起手術的成功率是多少?」醫生回答他:「只有1%。」他很驚慌,但是醫生說:「沒事的,在你之前我已經治死過99個人了。」

這是一則嘲笑那些不懂「概率」的人的笑話,卻講出了「真隨機」和「偽隨機」之間的區別。

在四月末的時候,我曾寫過一篇 《你打 游戲 靠的是技術,還是運氣?》 ,其中就提及了「偽隨機」這個概念。當時受限於篇幅,沒有詳細展開解釋「偽隨機」的概念。前不久,在因國際邀請賽而備受關注的Dota2在最近一次的更新中,有這么一條更新內容: 「落空的負面效果和下坡攻擊的落空效果現在都採用偽隨機觸發」

那麼到底什麼是 「偽隨機」 呢?以及和「偽隨機」對應的 「真隨機」 又是什麼概念?

贗隨機數演算法(Pseudo-Random Number Generator,簡稱PRNG) 是計算機的一個術語——當然,它也可以被叫做「偽隨機數演算法」,只是為了方便與 游戲 中的「偽隨機數」進行區分,本文中統一稱作「贗隨機數演算法」。

眾所周知,計算機程序是由無數「0」和「1」兩種狀態構成的,如果一個狀態不是「0」,那就必定是「1」,頗有種非黑即白的味道。

因此,在計算機程序中,不存在「不確定」的數字,只有確定的「1」和「0」。基於這種特性,計算機無法生成「真正的(不確定的)隨機數」。

那麼在計算機中,需要生成或是使用到隨機數的時候怎麼辦呢? 通常是利用計算機抓取一些數值,然後將這些數值輸入至一個復雜演算法 (常用的演算法是同餘法和梅森旋轉演算法,有興趣的讀者可以自行查詢,這里就不展開講了) 當中,通過一系列運算得出一個數字,這就是平常說的贗隨機數了。

只要最初輸入的數值(初值)不變,那麼輸出的值都會是同一個值,這就證明了這個數並不隨機,只是看起來隨機而已。

換句話說,只要這個隨機數是由確定演算法生成的,那就是贗隨機數。

所以下一次在和朋友聊天時提到真隨機數、偽隨機數時,如果有人插嘴:「計算機只能生成偽隨機數,所以根本沒有什麼真隨機」,那你就可以霸氣側漏地說他是 「雲玩家」 了。

我們通常說的 真隨機 又名 「純隨機」(True Random Distribution) ,就是我們平常一直說的那種、一般意義上的「隨機」。

在真隨機中, 每一個事件都是相互獨立、服從真隨機分布的,不受其他事件的發生而改變 。比方說某款 游戲 為了吸引用戶,擁有這么一個隨機抽卡系統:每次抽卡時,都有1%的幾率抽出SSR卡片,這個概率服從真隨機分布。

回到我們最開始說的那個「治死99個」的笑話:我們一眼就能看出這個笑話的不合理性。但在抽卡 游戲 中,我們的大腦瞬間失去理智。有相當一部分玩家認為: 我連抽100次,總能抽到這張卡吧!

實際上,連抽100次卻抽不出1%的SSR卡的幾率是為(1-0.01)^100=36.6%,甚至還稍稍超過了1/3。將連抽數字上升至300,也仍有4.9%的幾率。

換句話說,假設有10000個玩家連抽100次,就有約3660個玩家抽不出這張SSR;10000個玩家連抽300次,也仍有約490個玩家抽不出這張SSR ——這對玩家的 游戲 體驗來說可以說是毀滅性的打擊。

盡管純隨機在數學上是無罪的,在代碼中更是明明白白、清清楚楚,但玩家抽不出卡可不會回想到初高中的數學課本, 而是首先懷疑幾率是否被策劃運營篡改、這背後又是否有骯臟的PY交易……

當然不僅僅是在抽卡系統當中如此。在一些競技性比較強的 游戲 中(比如War3、Dota2之中——英雄聯盟幾乎完全摘除了隨機系統,不在此列),連續數次的「走運」極大影響 游戲 的競技性和觀賞性。

比方說Dota中最著名的概率英雄虛空假面的技能「回到過去」: 使虛空假面有25%幾率完全躲避一次傷害。 受限於War3引擎,這個技能採用的是真隨機概率,在某個極端情況下(通常見於精彩集錦中),虛空假面能夠保持很低的血量承受多次傷害卻不死、最終反殺對手。這種帶給敵方極差 游戲 體驗的系統,因此也進入了計師們「整治范圍」之中。

為了避免極差的 游戲 體驗帶來的玩家數量流失,設計者們提出了「偽隨機」的概念: 在不確定性的隨機事件當中,通過一系列演算法使隨機事件均勻分布在多次事件當中,盡可能減少或消除極端情況的發生,以提高玩家的 游戲 體驗。

在設計師們的努力下,「偽隨機」應運而生,這里的偽隨機就和上文的贗隨機數演算法(PRNG)意義不同了。

製造「偽隨機」的方法有很多,在War3、Dota2這類 游戲 當中普遍使用的是 「偽隨機分布」(Pseudo Random Distribution,簡稱PRD) 處理概率。

就拿Dota2中最強大的暴擊技能「恩賜解脫」來舉例: 幻影刺客有15%的幾率造成200%/325%/450%致命一擊傷害 。在PRD機制下,幻影刺客的攻擊實際上 並不是 每一刀都有15%的暴擊率。

根據PRD機制的公式P(N)=N*C可得出15%幾率的C值為3.22%,即幻影刺客的第一次攻擊暴擊概率為3.22%;如果第一刀沒有暴擊,則第二刀的暴擊率提升至2倍,即6.44%;如果仍舊沒有暴擊,則提升至3倍的9.66%,以此類推。

如果繼續推算,可得在第32刀時暴擊幾率會達到100%,最可能觸發暴擊的次數是第6刀,平均觸發刀數是6.67刀等等……

同樣,在連續觸發暴擊時,下一刀的暴擊幾率會減少。RPD機制使競技 游戲 中連續觸發或不觸發技能的幾率降低,避免了運氣成分過度干擾戰斗結果,大幅提升了玩家的 游戲 體驗,但不影響這些隨機事件的正反饋:TI6決賽的「打我五下暈三下」,可是令全球人民集體沸騰了呢!

除了偽隨機分布RPD之外,還有兩種常見的偽隨機: 洗牌演算法 組合隨機

洗牌演算法 最常見的用法,是在各大音樂播放器中的「隨機播放」之中。在隨機播放時,如果採用真隨機,會導致一首歌無論如何都播放不出,或是同一首歌連續播放數次(有興趣的讀者可以計算一下這些概率)。為了解決這個問題,播放器採用的解決方案即是洗牌演算法:將一個包含所有歌曲的數組像洗牌一樣打亂,然後依次播放這個亂序數組。

至於 組合隨機 ,這是一種廣泛應用於各個 游戲 的做法:在抽獎的時候進行兩次、或是更多次的判斷,一次不隨機,而剩下的判斷則是真隨機。比如說,你會在第X次抽卡時抽到SSR是確定的,但抽中的SSR具體是哪張卡,則是隨機的——這就是廣大手游中的「低保」系統了。

在一堆數據之中想要分清「真隨機」和「偽隨機」似乎並不是那麼容易。那麼接下來為大家介紹兩個例子,有助於更好理解什麼是「真隨機」和「偽隨機」:

真隨機 :有一天,小明在的班級上舉辦了一次抽獎活動。這個班級有40個學生,所以為了公平起見,保證每個學生都有1/40的幾率中獎,老師准備了40個相同的紙盒,每個紙盒中都有40張紙條,有1張紙條是中獎紙條。這樣一來,每個學生都有1/40的幾率中獎,但每個學生是否中獎並不受其他學生的影響。在極端情況下,這個班上可能40個學生都能中獎。這就是真隨機。

偽隨機 :小明班上舉辦了抽獎活動。為了公平起見,老師准備了1個紙盒,紙盒中有40張紙條,只有1張紙條是中獎紙條。這樣一來,每個學生都有1/40的幾率中獎——但是顯而易見,這個班上有且僅有一名學生能夠中獎。一名學生在中獎後,餘下的所有學生中獎幾率都會減少至0。這就是偽隨機。

㈦ 什麼是偽隨機數

大家可能很多次討論過隨機數在計算機中怎樣產生的問題,在這篇文章中,我會對這個問題進行更深入的探討,闡述我對這個問題的理解。

首先需要聲明的是,計算機不會產生絕對隨機的隨機數,計算機只能產生「偽隨機數」。其實絕對隨機的隨機數只是一種理想的隨機數,即使計算機怎樣發展,它也不會產生一串絕對隨機的隨機數。計算機只能生成相對的隨機數,即偽隨機數。

偽隨機數並不是假隨機數,這里的「偽」是有規律的意思,就是計算機產生的偽隨機數既是隨機的又是有規律的。怎樣理解呢?產生的偽隨機數有時遵守一定的規律,有時不遵守任何規律;偽隨機數有一部分遵守一定的規律;另一部分不遵守任何規律。比如「世上沒有兩片形狀完全相同的樹葉」,這正是點到了事物的特性,即隨機性,但是每種樹的葉子都有近似的形狀,這正是事物的共性,即規律性。從這個角度講,你大概就會接受這樣的事實了:計算機只能產生偽隨機數而不能產生絕對隨機的隨機數。

那麼計算機中隨機數是怎樣產生的呢?有人可能會說,隨機數是由「隨機種子」產生的。沒錯,隨機種子是用來產生隨機數的一個數,在計算機中,這樣的一個「隨機種子」是一個無符號整形數。那麼隨機種子是從哪裡獲得的呢?

下面看這樣一個C程序:

//rand01.c
#include

static unsigned int RAND_SEED;

unsigned int random(void)
{
RAND_SEED=(RAND_SEED*123+59)%65536;
return(RAND_SEED);
}

void random_start(void)
{
int temp[2];
movedata(0x0040,0x006c,FP_SEG(temp),FP_OFF(temp),4);
RAND_SEED=temp[0];
}

main()
{
unsigned int i,n;
random_start();
for(i=0;i<10;i++)
printf("%u\t",random());
printf("\n");
}

這個程序(rand01.c)完整地闡述了隨機數產生的過程:
首先,主程序調用random_start()方法,random_start()方法中的這一句我很感興趣:

movedata(0x0040,0x006c,FP_SEG(temp),FP_OFF(temp),4);

這個函數用來移動內存數據,其中FP_SEG(far pointer to segment)是取temp數組段地址的函數,FP_OFF(far pointer to offset)是取temp數組相對地址的函數,movedata函數的作用是把位於0040:006CH存儲單元中的雙字放到數組temp的聲明的兩個存儲單元中。這樣可以通過temp數組把0040:006CH處的一個16位的數送給RAND_SEED。

random用來根據隨機種子RAND_SEED的值計算得出隨機數,其中這一句:

RAND_SEED=(RAND_SEED*123+59)%65536;

是用來計算隨機數的方法,隨機數的計算方法在不同的計算機中是不同的,即使在相同的計算機中安裝的不同的操作系統中也是不同的。我在linux和windows下分別試過,相同的隨機種子在這兩種操作系統中生成的隨機數是不同的,這說明它們的計算方法不同。

現在,我們明白隨機種子是從哪兒獲得的,而且知道隨機數是怎樣通過隨機種子計算出來的了。那麼,隨機種子為什麼要在內存的0040:006CH處取?0040:006CH處存放的是什麼?

學過《計算機組成原理與介面技術》這門課的人可能會記得在編制ROM BIOS時鍾中斷服務程序時會用到Intel 8253定時/計數器,它與Intel 8259中斷晶元的通信使得中斷服務程序得以運轉,主板每秒產生的18.2次中斷正是處理器根據定時/記數器值控制中斷晶元產生的。在我們計算機的主機板上都會有這樣一個定時/記數器用來計算當前系統時間,每過一個時鍾信號周期都會使記數器加一,而這個記數器的值存放在哪兒呢?沒錯,就在內存的0040:006CH處,其實這一段內存空間是這樣定義的:

TIMER_LOW DW ? ;地址為 0040:006CH
TIMER_HIGH DW ? ;地址為 0040:006EH
TIMER_OFT DB ? ;地址為 0040:0070H

時鍾中斷服務程序中,每當TIMER_LOW轉滿時,此時,記數器也會轉滿,記數器的值歸零,即TIMER_LOW處的16位二進制歸零,而TIMER_HIGH加一。rand01.c中的

movedata(0x0040,0x006c,FP_SEG(temp),FP_OFF(temp),4);

正是把TIMER_LOW和TIMER_HIGH兩個16位二進制數放進temp數組,再送往RAND_SEED,從而獲得了「隨機種子」。

現在,可以確定的一點是,隨機種子來自系統時鍾,確切地說,是來自計算機主板上的定時/計數器在內存中的記數值。這樣,我們總結一下前面的分析,並討論一下這些結論在程序中的應用:

1.隨機數是由隨機種子根據一定的計算方法計算出來的數值。所以,只要計算方法一定,隨機種子一定,那麼產生的隨機數就不會變。

看下面這個C++程序:

//rand02.cpp
#include
#include
using namespace std;

int main()
{
unsigned int seed=5;
srand(seed);
unsigned int r=rand();
cout< // 編輯者註:可能代碼有缺
}

在相同的平台環境下,編譯生成exe後,每次運行它,顯示的隨機數都是一樣的。這是因為在相同的編譯平台環境下,由隨機種子生成隨機數的計算方法都是一樣的,再加上隨機種子一樣,所以產生的隨機數就是一樣的。

2.只要用戶或第三方不設置隨機種子,那麼在默認情況下隨機種子來自系統時鍾(即定時/計數器的值)

看下面這個C++程序:

//rand03.cpp
#include
#include
using namespace std;

int main()
{
srand((unsigned)time(NULL));
unsigned int r=rand();
cout< return 0;
}

這里用戶和其他程序沒有設定隨機種子,則使用系統定時/計數器的值做為隨機種子,所以,在相同的平台環境下,編譯生成exe後,每次運行它,顯示的隨機數會是偽隨機數,即每次運行顯示的結果會有不同。

3.建議:如果想在一個程序中生成隨機數序列,需要至多在生成隨機數之前設置一次隨機種子。

看下面這個用來生成一個隨機字元串的C++程序:

//rand04.cpp
#include
#include
using namespace std;
int main()
{
int rNum,m=20;
char *ch=new char[m];

for ( int i = 0; i //大家看到了,隨機種子會隨著for循環在程序中設置多次
srand((unsigned)time(NULL));
rNum=1+(int)((rand()/(double)RAND_MAX)*36); //求隨機值
switch (rNum){
case 1: ch[i]='a';
break ;
case 2: ch[i]='b';
break ;
case 3: ch[i]='c';
break ;
case 4: ch[i]='d';
break ;
case 5: ch[i]='e';
break ;
case 6: ch[i]='f';
break ;
case 7: ch[i]='g';
break ;
case 8: ch[i]='h';
break ;
case 9: ch[i]='i';
break ;
case 10: ch[i]='j';
break ;
case 11: ch[i]='k';
break ;
case 12: ch[i]='l';
break ;
case 13: ch[i]='m';
break ;
case 14: ch[i]='n';
break ;
case 15: ch[i]='o';
break ;
case 16: ch[i]='p';
break ;
case 17: ch[i]='q';
break ;
case 18: ch[i]='r';
break ;
case 19: ch[i]='s';
break ;
case 20: ch[i]='t';
break ;
case 21: ch[i]='u';
break ;
case 22: ch[i]='v';
break ;
case 23: ch[i]='w';
break ;
case 24: ch[i]='x';
break ;
case 25: ch[i]='y';
break ;
case 26: ch[i]='z';
break ;
case 27:ch[i]='0';
break;
case 28:ch[i]='1';
break;
case 29:ch[i]='2';
break;
case 30:ch[i]='3';
break;
case 31:ch[i]='4';
break;
case 32:ch[i]='5';
break;
case 33:ch[i]='6';
break;
case 34:ch[i]='7';
break;
case 35:ch[i]='8';
break;
case 36:ch[i]='9';
break;
}//end of switch
cout< }//end of for loop

cout< return 0;
}

而運行結果顯示的隨機字元串的每一個字元都是一樣的,也就是說生成的字元序列不隨機,所以我們需要把srand((unsigned)time(NULL)); 從for循環中移出放在for語句前面,這樣可以生成隨機的字元序列,而且每次運行生成的字元序列會不同(呵呵,也有可能相同,不過出現這種情況的幾率太小了)。
如果你把srand((unsigned)time(NULL));改成srand(2);這樣雖然在一次運行中產生的字元序列是隨機的,但是每次運行時產生的隨機字元序列串是相同的。把srand這一句從程序中去掉也是這樣。

此外,你可能會遇到這種情況,在使用timer控制項編製程序的時候會發現用相同的時間間隔生成的一組隨機數會顯得有規律,而由用戶按鍵command事件產生的一組隨機數卻顯得比較隨機,為什麼?根據我們上面的分析,你可以很快想出答案。這是因為timer是由計算機時鍾記數器精確控制時間間隔的控制項,時間間隔相同,記數器前後的值之差相同,這樣時鍾取值就是呈線性規律的,所以隨機種子是呈線性規律的,生成的隨機數也是有規律的。而用戶按鍵事件產生隨機數確實更呈現隨機性,因為事件是由人按鍵引起的,而人不能保證嚴格的按鍵時間間隔,即使嚴格地去做,也不可能完全精確做到,只要時間間隔相差一微秒,記數器前後的值之差就不相同了,隨機種子的變化就失去了線性規律,那麼生成的隨機數就更沒有規律了,所以這樣生成的一組隨機數更隨機。這讓我想到了各種晚會的抽獎程序,如果用人來按鍵產生幸運觀眾的話,那就會很好的實現隨機性原則,結果就會更公正。

最後,我總結兩個要點:
1.計算機的偽隨機數是由隨機種子根據一定的計算方法計算出來的數值。所以,只要計算方法一定,隨機種子一定,那麼產生的隨機數就是固定的。
2.只要用戶或第三方不設置隨機種子,那麼在默認情況下隨機種子來自系統時鍾。

熱點內容
神秘顧客訪問 發布:2025-05-15 20:33:39 瀏覽:296
安卓市場手機版從哪裡下載 發布:2025-05-15 20:17:28 瀏覽:814
幼兒速演算法 發布:2025-05-15 20:15:08 瀏覽:86
best把槍密碼多少 發布:2025-05-15 20:13:42 瀏覽:548
android安裝程序 發布:2025-05-15 20:13:20 瀏覽:559
c語言跳出死循環 發布:2025-05-15 20:06:04 瀏覽:824
a19處理器相當於安卓哪個水平 發布:2025-05-15 20:05:29 瀏覽:639
榮耀9i安卓強行關機按哪個鍵 發布:2025-05-15 20:00:32 瀏覽:750
密碼鎖寫什麼最好 發布:2025-05-15 19:05:31 瀏覽:783
5的源碼是 發布:2025-05-15 19:04:07 瀏覽:719