최댓값을 포함하는 정수 난수 생성시 round를 쓰지 않는 이유

kich555·2021년 9월 4일
2

뻘짓

목록 보기
1/1
post-thumbnail

서론

JavaScript를 공부한다면 아래와 같은 문제를 한번쯤을 접해봤을것이다.

1. 랜덤 수 생성하기
2. 원하는 범위 내에서의 랜덤 숫자 출력하기

자바스크립트를 사용해서 랜덤 숫자 구하는 게시글만 13만개에 달한다.

위 예제들을 보다보면 한가지 의문점을 가질 수 있을것이다.

왜 소숫점 자리를 처리하는데 Math.floor()만 사용하는것일까?

Math.floor(Math.random())

혹은

Math.floor(Math.random() * (max - min + 1)) + min;

과 같이 말이다.

심지어 최댓값을 포함한 랜덤 수를 얻고자 할때,
Math.floor()를 사용한다면 아래와 같이 +1을 할 수 밖에 없다.

Math.floor(Math.random() * (max - min + 1)) + min;
//아니 이거 나만 불편해?//.
Math.round(Math.random() * (max - min)) + min;
//편 - 안


Math.round()를 사용하고 싶었지만, 다들 Math.floor()를 쓰길래 일단 개 초보인 나는 법도를 따르기로 했다.

아 ㅋㅋ 뭔 이유가 있겠지.

머릿속에선 여전히 찜찜해 하고 있던 와중, 스터디하는 동료분들이 같은 의문점을 가진것 같아, 직접 그 이유를 찾아보기로 결심했다.

구글링

MDN을 비롯한 수많은 페이지들을 찾아보아도 모두 Math.floor()를 사용할 뿐 왜 Math.round()를 사용하지 않는지 알려주는곳이 없었다.

혹시나 Math.round()메소드가 나온지 얼마 안되서 글이 없는건가 해서 살펴봤는데..

당연하게도? 초창기 ECMA-262에부터 명시된 메소드였다.

초기 ECMA 명세에서부터 있는 기능을 굳이 안쓰는 이유는,
Math.random()Math.round()를 같이 쓰면 안되는 구체적인 이유가 있을것이라 확신하게된 계기가 되었다.


고민을 이어가던 도중 머릿속에 한 가정이 스쳐지나갔다.

혹시 Math.round()메소드와 랜덤 수 생성 확률 간에 어떤 관계가 있지 않을까?

한번 실험해보도록 하자.

실험

const per = {
  floor: [],
  ceil: [],
  round: []
} // 빈 객체를 만들고
function answer(){  
for(let i = 0; i<1000; i++){
  const ran = Math.random()*10;
  const a = Math.floor(ran);
  const b = Math.ceil(ran);
  const c = Math.round(ran); // floor ceil round 각각의 메소드에서 
  //0~10 사이의 수가 몇번씩 호출되는지 찾아보았다.

  
  per.floor[a] = per.floor[a]+1 || 1;
  per.ceil[b] = per.ceil[b]+1 || 1;
  per.round[c] = per.round[c]+1 || 1; //그리고 결과값을 빈 객체에 넣어본 결과
  }return per
}
answer();

흥미로운 결과가 나왔다.

per.ceil[0]`<1 empty item>`를 반환한건 당연히 ceil()은 올림이라 0을 반환하지 못하기 때문

위 결과값을 보면

floor[]index[10]의 값은 없지만 (내림이니까)
나머지 인덱스의 값들. 즉 랜덤으로 돌렸을때 0~9까지의 값이 고르게 분포된 것을 볼 수 있다.

ceil[]도 마찬가지로 반환되지 못하는 index[0]의 값을 제외한 나머지 값들은 고르게 분포되어 있는 것을 볼 수 있는데

round[]의 경우 다르다. 보면 index[0]index[10]의 값이 55와 49로 유난히 낮은것을 볼 수 있는데, 혹시나 해서 1000만번을 돌려보았다.

1000만번을 반복해도 여전히 round[]index[0]index[10]의 호출수는 낮은것을 볼 수 있다.

이는 처음에 예상했던 가설인 round()메소드와 랜덤 수 생성 확률의 관계가 확실하다는 소리인데,

왜 이런 현상이 나타나는 것일까?

는 정말 당연한 현상이라는 걸 계산식을 쓰고 실행해보고 깨달았다.

결론

  const ran = Math.random()*10;
  Math.floor(ran);
  Math.ceil(ran);
  Math.round(ran);

위 3가지는 0~10까지 정수 중 소숫점을 버림, 올림, 반올림 한 식이다.
즉 결과값은 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 중 하나가 나올 것이다.
여기서 각 수가 나올 확률의 범위가 어떻게 될까?

Math.floor() 같은 경우는 다음 수가 시작되기 직전까지의 모든 소수를 버린다.
ex) 1.99999999.... = 1
ex) 2.99999999.... = 2
즉 랜덤 최대값이 9.9999999이기 때문에 10은 존재할 수 없어도 모든 수의 구간이 동일하다.

Math.round() 같은 경우는 0.5를 기준으로 버릴지 올릴지 결정한다.
이러면 문제점이 해당 범위의 최소값과 최대값의 범위가 반으로 줄어들게된다.
ex) 0 ~ 0.4999999 = 0
ex) 0.5 ~ 1.49999 = 1
0의 범위는 0.499999밖에 되지 않지만
1의 범위는 0.999999가 된다.

💡즉 최대값과 최소값 수의 확률이 다를 수 밖에 없다.

결론

💡이러한 이유로 우리는Math.round()를 쓰지않고, Math.floor()를 쓰는것이다.

💡Math.ceil()-1을 사용한다면 원치않는 min-1의 값을 얻을 수 있기때문에 사용하지 않는다.

💡 굳이 뻘짓할 시간에 STACK OVERFLOW를 잘 찾아보자. (실험 끝마치고 블로깅 할 자료 찾다가 원하던 답이 있었음을 뒤늦게 알아차림

참고자료 :
💡STACK OVERFLOW Generating random whole numbers in JavaScript in a specific range?
💡MDN Web Docs random();

profile
const isInChallenge = true; const hasStrongWill = true; (() => { while (isInChallenge) { if(hasStrongWill) {return 'Success' } })();

0개의 댓글