본문으로 건너뛰기

JS 난수

무료2015-05-18#JS#js随机数#JavaScript随机数

JS 의 Math.random() 도 의사 난수를 생성하는데, 의사 난수를 더 랜덤하게 보이게 하려면 어떻게 해야 할까요?

一.문제 배경

2 차원 평면에 한 무리의 NPC 가 있으며, 각 라운드에서 랜덤하게 상/하/좌/우 중任一방향으로 1 보 이동할 수 있고, 유닛의 충돌 부피가 있습니다 (NPC 위치는 중복될 수 없음)

규칙은 이뿐이지만, 초기 상태에서 이 NPC 들은 인위적으로 2 차원 평면에 균일하게 분포되어 있으며, N 라운드 실행 후 모든 NPC 가 좌하각에 집중되어 있었습니다..왜 이럴까요, 랜덤하다고 했잖아요?

二.분석

기존 구현은 다음과 같습니다:

  1. NPC 의 현재 위치에 따라 이동 가능한 위치를 판단하고, 결과를 1 차원 배열 arr 에 저장합니다

P.S.상/하/좌/우는 최대 4 개의 점 (주변이 비어 있음), 최소 0 개의 점 (囲まれている)

  1. [0, arr.length - 1] 내의 난수를 생성하여 타겟 위치의 인덱스 값 index 로 사용합니다

     // 난수 생성 [min, max]
     w.Util.rand = function(min, max) {
    
         return Math.round(Math.random() * (max - min) + min);
     }
    
  2. NPC 를 arr[index] 위치로 이동시킵니다

로직에는 문제가 없어야 하는데, 왜 실행 결과는 NPC 가 모두 좌하각에 모여 회의를 하고 있는 걸까요? 잠깐, 왜 좌하각이고 다른 모서리가 아닐까요?

1 단계 구현이 상->하->좌->우 순서로 판단하고 있기 때문에, 마지막에 모두 좌하각으로 갔다는 것은 상과 우의 확률이 너무 작다는 것을 나타냅니다 (난수 생성 함수 rand 는 문제없으며, 확실히 [min, max] 의 숫자를 얻을 수 있습니다)

문제의 근원은 Math.random() 이役に立たずで、[0, 1) 사이의 소수를 생성하며, 0 이나 1 에 가까운 값을 얻을 확률이 매우 낮기 때문에, rand() 함수가 생성하는 난수가 min 이나 max 를 얻을 확률도 매우 낮고, 상/우로 이동할 가능성도 작아집니다

三.해결책

min 과 max 를 얻을 확률이 매우 낮고, 중간 확률이 비교적 균일하다면, 간단합니다. 이 2 개의 값을 잘라내면 됩니다. 구체적인 구현은 다음과 같습니다:

function randEx(min, max) {

    var num;
    var maxEx = max + 2; // 범위를 [min, max + 2] 로 확대하고, 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] 로 확대하고, 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 를 더하고 2 를 빼고, 6 을 더하고 3 을 빼고……만족할 때까지) 은 보편적이기 때문입니다

댓글

아직 댓글이 없습니다

댓글 작성