(轉(zhuǎn)載)Math.random()隨機(jī)數(shù)的二三事

原文地址:http://www.soulteary.com/2014/07/05/js-math-random-trick.html

看到題目脚仔,如果大家在平時(shí)被問到:
如何生成一個(gè)怎么樣怎么樣的整數(shù)隨機(jī)數(shù)丙挽,估計(jì)大家都會(huì)不屑明郭,但是當(dāng)你淡定的回答:

獲取一個(gè)范圍應(yīng)該是隨機(jī)數(shù)seeds和區(qū)間數(shù)值差的乘機(jī)與最小數(shù)相加然后再怎么怎么的時(shí)候…

有沒有發(fā)現(xiàn)你的思維已經(jīng)固化了呢。

這個(gè)知識(shí)點(diǎn)應(yīng)該是玩JS肯定會(huì)碰到的之一吧汗盘。

先來掉書袋,看看MDN的文檔
打開Node磁浇,進(jìn)入終端命令行模式,輸入Math.random():

 Math.random()
 0.436846193857491

結(jié)果是不是依舊如同往常一樣稀松平常的小于1的一個(gè)偽隨機(jī)數(shù)跳了出來呢朽褪。
這個(gè)時(shí)候置吓,如果別人問你,還有什么其他方案可以生成隨機(jī)數(shù)么缔赠,你會(huì)想到神馬呢衍锚。
如果你繼續(xù)在終端里輸入new Date()-0:

 new Date()-0
1404488829907

我想你可以得到一個(gè)自增的數(shù)字,對嗤堰,就是“秒”戴质,如果你說這貨哪里隨機(jī)了,請別著急:

 (new Date()-0)%10086
8657

這里的取模%的數(shù)值可以是大于2且最好小于當(dāng)前時(shí)間的數(shù)值踢匣,則可以得到你取模數(shù)值概率分之一的概率的隨機(jī)數(shù)告匠。

如果你取模的數(shù)值是隨機(jī)數(shù)呢,那么產(chǎn)生這個(gè)隨機(jī)數(shù)的可見的兩個(gè)變量都是隨機(jī)的离唬,那么是不是近似真的“隨機(jī)數(shù)”了呢后专?

當(dāng)然,如果使用這招输莺,還要考慮到硬件以及語言執(zhí)行過程的耗時(shí)行贪,因?yàn)槲覀冎烙?jì)算機(jī)執(zhí)行的時(shí)候,有一個(gè)時(shí)間的精度的范疇模闲,所以需要使用一點(diǎn)點(diǎn)的延時(shí)抑制建瘫。

扯了一些沒用的,你可能著急了尸折,那么請保持好奇心啰脚,我們繼續(xù)說點(diǎn)無聊的事情。

Math.random會(huì)提供給我們一個(gè)[0,1)之間的隨機(jī)數(shù)实夹,但是如果我們要[1,10]范圍隨機(jī)整數(shù)的話橄浓,可以使用以下三個(gè)函數(shù):

  • Math.round
  • Math.ceil
  • Math.floor

我們先來生成一個(gè)隨機(jī)數(shù):

 Math.random()*(10-1)+1
8.26644050120376

接著我們來使用這三個(gè)Math內(nèi)建函數(shù):

 Math.round(8.26644050120376)
8
 Math.ceil(8.26644050120376)
9
 Math.floor(8.26644050120376)
8

把數(shù)值換成8.56644050120376后,再來看看:

Math.round(8.56644050120376)
9
 Math.ceil(8.56644050120376)
9
 Math.floor(8.56644050120376)
8

所以區(qū)別一目了然亮航,對于浮點(diǎn)數(shù)荸实,round會(huì)遵守四舍五入規(guī)則,ceil無論如何貪心進(jìn)位+1缴淋,floor無論如何都小心翼翼的自斷一臂-1准给,至于整數(shù)泄朴,自己試試看咯。

說到這里露氮,接下來可以正常的描述內(nèi)容了:

問:如何快速生成一段隨機(jī)文本祖灰,比如驗(yàn)證碼或者我們訪問網(wǎng)站常見的隨機(jī)數(shù)token。

答案很多畔规,我說一個(gè)經(jīng)典的局扶,其實(shí)思路很簡單,把剛剛生成隨機(jī)數(shù)的方法隨便選擇一個(gè).toString():

Math.random().toString(36).substring(7);
//當(dāng)然也可以寫成這樣
Math.random().toString(36).slice(2);
//或者利用時(shí)間
(new Date()-0).toString(36)

隨便輸出一些叁扫,我們可以看到這貨輸出的字符串長短參次不齊的:

mptzulnb3xr
87jx7vkuik9
761qsolayvi
amqx2mx6r
ce5uyvkuik9
5ioufim5cdi
dirp4hiwwmi
ioe597ldi
ohn9izfr
sprsakk2o6r
5g3ruo6flxr

//單純時(shí)間來做隨機(jī)是不是生成的慘不忍睹
//而且不做隨機(jī)延時(shí)抑制三妈,重復(fù)太明顯

hx7pom3y
hx7pom3z
hx7pom40
hx7pom41
hx7pom42
hx7pom43
hx7pom44
hx7pom45

我們先來看看為什么用toString()可以生成隨機(jī)數(shù):
首先前面的家伙不管是隨機(jī)數(shù)seeds生成的,還是時(shí)間遞增的長整型莫绣,它們都是Number構(gòu)造器構(gòu)造出來的[object Number]沈跨,而在ecma.js中,Number的這個(gè)方法是這樣的:

/**
@param {Number} [radix]
@return {string}
*/
Number.prototype.toString = function(radix) {};

作為一個(gè)好人兔综,我給你指條明路,MDN的文檔
看過之后是不是想到了parseInt
函數(shù)的第二個(gè)參數(shù)狞玛?我們發(fā)現(xiàn)软驰,這個(gè)原生函數(shù)支持2~36(如果超出36,那么26個(gè)字母就不夠用了親)進(jìn)制的轉(zhuǎn)換心肪,所以如果在生成的時(shí)候锭亏,隨機(jī)切換進(jìn)制,取結(jié)果的隨機(jī)位置硬鞍,效果會(huì)不會(huì)更好呢慧瘤,你可以試一試。
如果你想獲得字母多一點(diǎn)且平均一點(diǎn)固该,那么只有使用36進(jìn)制了锅减,但是不管怎么躲,都有可能出現(xiàn)一串?dāng)?shù)字伐坏。

xjuk0zxofen1xlxr

有沒有好方法來解決這個(gè)問題呢怔匣,答:

Math.random().toString(36).replace(/[^a-z]+/g, '')
//或者這樣
Math.random().toString(36).slice(2).replace(/\d/g, '')

這個(gè)時(shí)候,你或許會(huì)說桦沉,文章該就此結(jié)束了吧每瞒,這個(gè)方法看起來很爽很簡潔。
不過纯露,你有思考過一個(gè)問題么剿骨,回顧前文,隨機(jī)數(shù)可以是0,1,...這些整數(shù)…

當(dāng)隨機(jī)數(shù)是這些數(shù)值的時(shí)候埠褪,很抱歉浓利,返回值是原來的數(shù)值挤庇,即0,1,...,我們得到的最后的結(jié)果就會(huì)是一個(gè)空字符串荞膘,而如果是0.5這類某些以5結(jié)尾的浮點(diǎn)數(shù)的時(shí)候罚随,結(jié)果依然如此。還有當(dāng)數(shù)值是某些時(shí)候羽资,生成的隨機(jī)數(shù)位數(shù)會(huì)比較短…

解決這個(gè)問題淘菩,你當(dāng)然可以重新生成這個(gè)隨機(jī)數(shù),直到它輸出一個(gè)你心滿意足的隨機(jī)數(shù)再放過他屠升,但是潮改,我們剛剛了解到的生成一個(gè)大的隨機(jī)數(shù)的方法是依賴時(shí)間,小學(xué)還是初中學(xué)過的用一個(gè)比較小的數(shù)值除以一個(gè)比較大的數(shù)值腹暖,得到的結(jié)果是一個(gè)更小的浮點(diǎn)數(shù)來解決這個(gè)問題呢汇在。

(Math.random() / +new Date()).toString(36).replace(/\d/g, '').slice(1)

這樣就得到了一個(gè)比較長,且比較公平的隨機(jī)數(shù)脏答「庋常可以用node驗(yàn)證一下:

var c = {}, r;
for (var i = 0, j = 10000000; i < j; i++) {
r = (Math.random() / +new Date()).toString(36).replace(/\d/g, '').slice(1);
c[r] ? c[r] += 1 : c[r] = 1;}
for (var i in c) {if (c[i] === 1) {delete c[i];}}
console.log(c);

運(yùn)行結(jié)果是沒有任何沖突,當(dāng)然這可能是小概率事件殖告。如果你覺得你點(diǎn)很背阿蝶,你可以試一試下面這段,手動(dòng)執(zhí)行幾次黄绩,看看缠犀,有沒有不是空數(shù)組這個(gè)結(jié)果的結(jié)果朴则。

for(var i = 0,j=100;i<j;i++){
(function(){var c = {}, r;for (var i = 0, j = 1000; i < j; i++) {
r = (Math.random() / +new Date()).toString(36).replace(/\d/g, '').slice(1);
c[r] ? c[r] += 1 : c[r] = 1;}
for (var i in c) {if (c[i] === 1) {delete c[i];}}
console.log(c);}())}

當(dāng)然笋颤,你也可以不用這兩個(gè)測試?yán)由厦娴哪嵌未a绿满,改用下面這種方式,多輸出幾次隨機(jī)字符串粤蝎,然后拼合在一起真仲。

for(var c = ''; c.length < 5;) c += Math.random().toString(36).substr(2)

這個(gè)把戲估計(jì)你看膩了,我們來看下面這個(gè)系列的示例初澎,從固定的字典中抽取字符構(gòu)成隨機(jī)字符串:

依賴Array的map方法袒餐,要注意兼容性,當(dāng)然谤狡,你從MDN那邊copy一段hacks也可以無縫兼容灸眼,是不是看起來高大上一點(diǎn):

Array.apply(0, Array(5)).map(function () {
    return (function (charset) {
        return charset.charAt(Math.floor(Math.random() * charset.length))
    }('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'));
}).join('');

不過或許你更容易接受這種多一點(diǎn):

function rand(length, current) {
    current = current ? current : '';
    return length ? rand(--length, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz".charAt(Math.floor(Math.random() * 60)) + current) : current;
}

或者更傳統(tǒng)的:

function rand() {
    var text = "";
    var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
    for (var i = 0; i < 5; i++) text += possible.charAt(Math.floor(Math.random() * possible.length));
    return text;
}

或許你以為這樣把戲就玩完了,too young too simple墓懂,之前玩過了Number的原生函數(shù)焰宣,我們還可以動(dòng)手腳的是String的原生函數(shù),比如我們知道捕仔,字母的ASCII碼是[65,97]匕积,那么當(dāng)這個(gè)區(qū)間是字典盈罐,我們隨機(jī)抽取這個(gè)區(qū)間是不是會(huì)有一些好玩的事情發(fā)生呢。

function rand(x) {
    var s = "";
    while (s.length < x && x > 0) {
        var r = Math.random();
        s += String.fromCharCode(Math.floor(r * 26) + (r > 0.5 ? 97 : 65));
    }
    return s;
}

隨便輸入一個(gè)電話號(hào)碼(推薦輸入短一點(diǎn)的數(shù))闪唆,然后等等看盅粪,是不是會(huì)出現(xiàn)下面的結(jié)果:

JrxKnxrHwJzGCKoKCzrpFxMxDBFqBrpAEGvpvMtCIHIHFCxtMxrHtpEGDyxzxwMBBqDvEBwxprwHqDMCErIzLwuyFApnxpxJoxBAFEoHsAEqvIxBIJvpFBoLnvurHJsvDFwGtFvDsELMLwzowvqBJtCwrGCsCHGvsxCLunGxtrtnyvvwyFqEsstotxnsrqLHAIyCxzLxDqtuzsoFJAGHyxrwxJusJrtpuvIyIILoJrGLKnptqHLBAKwGEpnIwzCtFAnrHIqLHynGwuyupsDpLJFGxBuJBouwCDGKsKFCLKnAzoupsxFqIynKFCoBApyBsJKzJpKwEFCyGywrBoFpvorMzBrBAFowrKxvuJLoKtzpEoDsEsBExyGBMssnADnBwrvJDGunJsMyFGCxIFApEnoLyGxBrHroBsLAICGLDIwvqp

這個(gè)話題,好像說的有點(diǎn)麻木了悄蕾,換個(gè)需求吧票顾。有的網(wǎng)站會(huì)玩一些隨機(jī)背景色的小花樣。有沒有優(yōu)雅的解決方案呢:

遵守隨機(jī)數(shù)函數(shù)的定義帆调,獲取顏色數(shù)值之間的數(shù)值就好奠骄,看過了上面的代碼,這句番刊,很好懂了伐:

'#'+(0x1000000+(Math.random())*0xffffff).toString(16).substr(1,6);

當(dāng)然含鳞,你可以寫一個(gè)更加直觀的方案:

//用十進(jìn)制數(shù)據(jù)來代替16進(jìn)制數(shù)據(jù)運(yùn)算,請注意因?yàn)橛疫吺情_區(qū)間芹务,要比上面的0xffffff +1蝉绷,最后將結(jié)果轉(zhuǎn)換回十六進(jìn)制,然后修剪一下字符串枣抱,輸出
Math.floor(Math.random() * 16777216).toString(16);
'#000000'.slice(0, -color.length) + color;

//或者更簡短的樣子如下
"#" + ("000000" + Math.floor(Math.random() * 16777216).toString(16)).substr(-6);

還有一類花樣熔吗,如下:

function randomColor() {
    var r = function () { return Math.floor(Math.random()*256) };
    return "rgb(" + r() + "," + r() + "," + r() + ")";
}

輸出如下:

rgb(29,236,191)

時(shí)間不早了,最后說一下隨機(jī)排序數(shù)組吧沃但。

關(guān)于洗牌算法,網(wǎng)上流傳很多佛吓,隨便選擇一種模擬一下就好宵晚,比如隨便寫的全重排:

var i = 0, data = [], r;
for (; i < 10; data[i++] = i);
while (--i) {
    r = Math.round(Math.random() * 9 + 1) - 1;
    data[i] = data[i] + data[r], data[r] = data[i] - data[r], data[i] = data[i] - data[r];
}
console.log(data)

或者利用Array.prototype.sort()函數(shù),這里可以不把里面的數(shù)值帶進(jìn)來運(yùn)算维雇。

首先Math.random()會(huì)生成一個(gè)[0,1)之間的數(shù)值淤刃,用0.5這個(gè)比較公平的數(shù)值減去它,概率得到小于0吱型,等于0,大于0三種狀況逸贾,而Array.prototype.sort()期待的數(shù)值恰好是[-1,0,1],是不是很省事津滞。

var i = 0, data = [], r;
for (; i < 10; data[i++] = i);
data.sort(function () {
    return .5 - Math.random();
});
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末铝侵,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子触徐,更是在濱河造成了極大的恐慌咪鲜,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件撞鹉,死亡現(xiàn)場離奇詭異疟丙,居然都是意外死亡颖侄,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進(jìn)店門享郊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來览祖,“玉大人,你說我怎么就攤上這事炊琉≌沟伲” “怎么了?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵温自,是天一觀的道長玄货。 經(jīng)常有香客問我,道長悼泌,這世上最難降的妖魔是什么松捉? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮馆里,結(jié)果婚禮上隘世,老公的妹妹穿的比我還像新娘。我一直安慰自己鸠踪,他們只是感情好丙者,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著营密,像睡著了一般械媒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上评汰,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天纷捞,我揣著相機(jī)與錄音,去河邊找鬼被去。 笑死主儡,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的惨缆。 我是一名探鬼主播糜值,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼坯墨!你這毒婦竟也來了寂汇?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤捣染,失蹤者是張志新(化名)和其女友劉穎健无,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體液斜,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡累贤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了臼膏。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片硼被。...
    茶點(diǎn)故事閱讀 38,577評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖渗磅,靈堂內(nèi)的尸體忽然破棺而出嚷硫,到底是詐尸還是另有隱情,我是刑警寧澤始鱼,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布仔掸,位于F島的核電站,受9級特大地震影響医清,放射性物質(zhì)發(fā)生泄漏起暮。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一会烙、第九天 我趴在偏房一處隱蔽的房頂上張望负懦。 院中可真熱鬧,春花似錦柏腻、人聲如沸纸厉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽颗品。三九已至,卻和暖如春沃缘,著一層夾襖步出監(jiān)牢的瞬間躯枢,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工孩灯, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留闺金,地道東北人逾滥。 一個(gè)月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓峰档,卻偏偏與公主長得像,于是被迫代替她去往敵國和親寨昙。 傳聞我的和親對象是個(gè)殘疾皇子讥巡,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評論 2 348

推薦閱讀更多精彩內(nèi)容

  • 方法1 (數(shù)據(jù)類型)(最小值+Math.random()*(最大值-最小值+1)) 例: (int)(1+Math...
    GB_speak閱讀 40,969評論 2 6
  • 第5章 引用類型(返回首頁) 本章內(nèi)容 使用對象 創(chuàng)建并操作數(shù)組 理解基本的JavaScript類型 使用基本類型...
    大學(xué)一百閱讀 3,216評論 0 4
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)舔哪,斷路器欢顷,智...
    卡卡羅2017閱讀 134,628評論 18 139
  • 1. 常用那幾種瀏覽器測試?有哪些內(nèi)核(Layout Engine)?(Q1)瀏覽器:IE捉蚤,Chrome抬驴,F(xiàn)ire...
    猿分讓我們相遇閱讀 238評論 0 0
  • 那是柱子最酷的一晚炼七,單槍匹馬,手舉板凳布持,大腳破門豌拙,以一敵四,如天神下凡题暖,把徐峰一伙打得嗷嗷直叫按傅,就像高舉大劍怒吼著...
    阿姆斯特高閱讀 820評論 9 5