JS更隨機的隨機數

一.問題背景

一個二維平面上有一羣NPC,每一回合能夠隨機向上/下/左/右任一方向走1步,有單位碰撞體積(NPC位置不能重合)javascript

規則就這麼簡單,初始狀況下這羣NPC是被人工均勻分佈在二維平面上的,運行N個回合後發現全部NPC都集中在了左下角。。怎麼會這樣,說好的隨機呢?java

二.分析

現有的實現是這樣的:數組

  1. 根據NPC的當前位置判斷獲得能夠去的位置,把結果存放在一維數組arr裏dom

    P.S.上/下/左/右最多4個點(周圍空蕩蕩的),最少0個點(被圍起來了)wordpress

  2. 生成[0, arr.length – 1]內的一個隨機數,做爲目標位置索引值index函數

    // 生成隨機數[min, max]
    w.Util.rand = function(min, max) {
    
        return Math.round(Math.random() * (max - min) + min);
    }
  3. 控制NPC移動到arr[index]的位置測試

邏輯應該是沒有問題的,但是爲何運行結果是NPC都跑到左下角開會去了呢?等等,爲何是左下而不是其它角角?.net

由於實現第一步的時候是按照上 -> 下 -> 左 -> 右的順序判斷的,最後都去了左下角,說明向上和向右的機率過小了(生成隨機數的函數rand是沒問題的,確實能獲得[min, max]的數)3d

問題的根源是Math.random()不給力,生成[0, 1)之間的小數,取到0和靠近1的值機率很小,因此rand()函數生成的隨機數取到min和mix的機率也很小,向上/向右的可能性也就小了blog

三.解決方案

既然取到min和max的機率很小,中間機率比較均勻,那好辦,切掉這兩個值就行了。具體實現以下:

function randEx(min, max) {

    var num;
    var maxEx = max + 2; // 擴大範圍到[min, max + 2],引入兩個多餘值替換min/max

    do{

        num = Math.round(Math.random() * (maxEx - min) + min);
        num--;
    } while (num < min || num > max);   // 範圍不對,繼續循環

    return num;
}

值得一提的是上面的加2減1比較巧妙,可以剛好排除min和max

四.運行結果

JS中Math.random()返回值的機率差別可能比你想象的要大些,在實際應用中是不可接受的

測試代碼以下:

// 生成隨機數[min, max]
w.Util.rand = function(min, max) {

    return Math.round(Math.random() * (max - min) + min);
}

// 生成更隨機的隨機數[min, max]
function randEx(min, max) {

    var num;
    var maxEx = max + 2; // 擴大範圍到[min, max + 2],引入兩個多餘值替換min/max

    do{

        num = Math.round(Math.random() * (maxEx - min) + min);
        num--;
    } while (num < min || num > max);   // 範圍不對,繼續循環

    return num;
}

function testRand(times, fun) {

    var arr = [1, 2, 3, 4];
    var count = [];
    var randVal;
    for (var i = 0; i < times; i++) {

        // 獲取[0, 3]的隨機數
        randVal = fun(0, arr.length - 1);
        // 記錄次數
        if (typeof count[randVal] !== "number") {

            count[randVal] = 0;
        }
        count[randVal]++;
    }

    console.log(count);
}

console.log("100 times");
testRand(100, w.Util.rand); // 以前的實現
testRand(100, randEx);      // 改進過的實現

console.log("1000 times");
testRand(1000, w.Util.rand); // 以前的實現
testRand(1000, randEx);      // 改進過的實現

console.log("10000 times");
testRand(10000, w.Util.rand); // 以前的實現
testRand(10000, randEx);      // 改進過的實現

console.log("100000 times");
testRand(100000, w.Util.rand); // 以前的實現
testRand(100000, randEx);      // 改進過的實現

運行結果以下:

random

看到了吧,效果仍是很不錯的

後話

理論上Java的Math.random(),C#的next可能也存在這個問題,這裏就沒有必要驗證了,由於randEx函數的思想(加2減1,嫌效果很差還能夠加4減二、加6減3……直到滿意爲止)是通用的

相關文章
相關標籤/搜索