본문 바로가기
개발/php, javascript

삼각 분포로 난수 발생시키기

by 매몰 2019. 10. 28.

가우시안 분포 즉, 정규분포로 난수를 발생시켜야 하는 경우가 종종 있다.

 

하지만 우리가 흔히 사용하는 rand() 함수는 균일 분포로 난수를 가져오므로 따로 구현해야 한다.

다행히 정규분포 난수 발생 소스는 인터넷상에서 쉽게 구할수 있기 때문에 별 문제 될 건 없다.

 

한 가지 딴지를 걸자면 정규분포는 피 적분이 안되기 때문에 반복문을 사용해 난수를 만들어야 한다.

만약, 아주 많은 난수를 한꺼번에 발생시킨다면, 부하가 걸릴 수 있다는 말이다.

물론, 아주 쓸데없는 걱정이다. 그정도로 21세기의 컴퓨터는 나약하지 않다ㅎㅎ

 

어쨌거나 반복문이 없는 확률 밀도 기반 난수를 쓰고 싶다는 어리석은 생각에

다음과 같은 삼각 분포 난수 함수를 만들어 보았다.

 

삼각형 그래프는 정적분이 쉬워서 면적을 비율 삼아 난수를 발생시키기 좋다.

 

 

function triangularRand($center, $range, $gradient = 1) {
    //중심값이 0이 되도록 이동 시킨후 난수를 구하고 다시 되돌림
    //근의 공식에 의한 값이 항상 실수가 되게 하기 위함
    return triangularOriginRand(0, $range, $range * $gradient) + $center;
}

function triangularOriginRand($center, $range, $top) {
    $total = $top * $range; //그래프 총면적
    $area = mt_rand(0, $total); //면적 난수
    $a = ($top / $range) / 2.0; //정적분 x^2 계수
    $start = $center - $range; //최소값
    $end = $center + $range; //최대값
    
    //(최소값과 x 사이의 정적분 = 면적 난수) 방정식 구함
    //x가 발생시킬 난수
    $half = $total / 2;
    if ($area < $half) {
        //왼쪽 그래프
        $value = quadraticRoot($a, $top, -($a * pow($start, 2)) - ($top * $start) - $area);
        if ($value != null) {
            return round($value[0] >= $start && $value[0] <= $center ? $value[0] : $value[1]);
        }
    }
    else if ($area > $half) {
        //오른쪽 그래프
        $value = quadraticRoot(-$a, $top, ($a * pow($center, 2)) - ($top * $center) - ($area - $half));
        if ($value != null) {
            return round($value[0] >= $center && $value[0] <= $end ? $value[0] : $value[1]);
        }
    }
    
    return $center;
}

//근의 공식
private function quadraticRoot($a, $b, $c) {
    $d = pow($b, 2) - (4 * $a * $c);
    if ($d < 0) {
        return null;
    }
    
    $d = sqrt($d);
    $a2 = 2 * $a;

    return Array((-$b + $d) / $a2, (-$b - $d) / $a2);
}

 

 

 

0에서 100까지의 난수를 200개 발생시키고 각 범위 안의 개수를 확인해보자

echo nl2br("난수\n");

$name = Array('00~19:','20~39:','40~59:','60~79:','80~00:');
$range = array_fill(0, 5, 0);
for ($i = 0; $i < 200; $i++) {
    //중심값 50, 양쪽 범위 각각 50
    //즉, 0 ~ 100 사이의 난수 발생
    $value = triangularRand(50, 50);
    if ($value < 20) {
        $range[0]++;
    }
    else if ($value < 40) {
        $range[1]++;
    }
    else if ($value < 60) {
        $range[2]++;
    }
    else if ($value < 80) {
        $range[3]++;
    }
    else {
        $range[4]++;
    }
    
    echo "$value, ";
    
    if (($i + 1) % 10 == 0) {
        echo nl2br("\n");
    }
}

echo nl2br("\n난수 갯수\n");
foreach ($range as $i=>$item) {
    echo nl2br("$name[$i] $item\n");
}

 

 

 

결과 출력!

-----------

난수
58, 47, 58, 41, 57, 44, 65, 9, 40, 39, 
60, 57, 51, 46, 77, 24, 51, 76, 47, 38, 
70, 41, 47, 92, 32, 69, 64, 43, 37, 48, 
74, 73, 59, 11, 86, 95, 31, 19, 66, 69, 
55, 29, 35, 67, 22, 80, 28, 58, 57, 43, 
50, 77, 42, 19, 72, 40, 33, 36, 53, 22, 
19, 29, 46, 95, 54, 79, 26, 69, 32, 69, 
65, 25, 42, 47, 8, 52, 43, 70, 46, 64, 
29, 40, 23, 54, 77, 81, 35, 53, 67, 49, 
47, 24, 54, 33, 52, 89, 22, 50, 44, 15, 
17, 53, 58, 45, 44, 61, 54, 46, 46, 35, 
27, 9, 51, 55, 79, 55, 52, 80, 49, 14, 
57, 15, 74, 71, 55, 41, 75, 46, 74, 14, 
73, 81, 49, 68, 50, 48, 63, 77, 43, 87, 
37, 82, 63, 43, 55, 45, 56, 78, 9, 41, 
25, 24, 40, 24, 46, 51, 57, 58, 52, 58, 
46, 73, 11, 64, 27, 39, 74, 22, 45, 53, 
31, 38, 42, 64, 16, 85, 70, 32, 91, 68, 
16, 23, 63, 64, 18, 23, 87, 36, 32, 24, 
59, 86, 30, 78, 47, 51, 72, 29, 22, 6, 

난수 갯수
00~19: 18
20~39: 43
40~59: 80
60~79: 44
80~00: 15

------------

 

중심값에 가까울수록 더 많은 난수가 분포되어 있다.

 

 

 

 

 

도움이 되셨다면~ 정성으로 빚은 저희 앱!  많은 이용 바래요:)

 

https://meorimal.com/index.html?tab=spaceship

 

우주선 - 방치형 인공지능 투자 체험기

미리 맛보는 인공지능 투자!

(주)머리말 meorimal.com

 

https://meorimal.com/subway.html

 

지하철어디있니

더이상 고민하지 마세요. 뛸지 말지 딱 보면 알죠.

(주)머리말 meorimal.com

 

사업자 정보 표시
주식회사 머리말 | 고영진 | 서울특별시 송파구 중대로 135 서관 10층 (가락동, 아이티벤처타워) | 사업자 등록번호 : 524-88-00727 | TEL : 010-9990-3674 | Mail : gyjmeba@hanmail.net | 통신판매신고번호 : 2017-서울강남-03941호 | 사이버몰의 이용약관 바로가기