공정한 랜덤 뽑기 방법
발표자 선정, 경품 추첨, 순서 정하기처럼 결과에 사람들의 이해관계가 걸릴수록 '정말 공정하게 뽑혔는가'가 중요해집니다. 겉으로는 무작위처럼 보여도 방법이 잘못되면 특정 항목이 구조적으로 더 자주 뽑히는 편향이 생깁니다. 이 글에서는 편향이 생기는 지점을 하나씩 짚고, 어떤 조건을 갖춰야 공정한 뽑기라고 부를 수 있는지 정리합니다.
무작위와 공정함은 다르다
무작위(random)는 결과를 예측할 수 없다는 뜻이고, 공정함(fairness)은 모든 항목이 정해진 규칙대로 같은(또는 의도한) 확률을 가진다는 뜻입니다. 이 둘은 자주 같은 말처럼 쓰이지만 전혀 다른 개념입니다. 예측 불가능하더라도 특정 항목이 구조적으로 유리하다면 그 뽑기는 무작위일 뿐 공정하지 않습니다.
예를 들어 30명 명단에서 '1번부터 30번 중 하나를 고른 뒤, 그 번호가 결석이면 다음 번호로 넘어간다'는 규칙을 쓴다고 해봅시다. 결과는 예측할 수 없지만, 결석자 바로 뒤 번호의 사람은 자기 번호와 앞사람 번호 두 경우에 뽑히므로 확률이 두 배가 됩니다. 무작위지만 공정하지 않은 전형적인 예입니다.
그래서 공정한 뽑기를 만들려면 두 가지가 동시에 필요합니다. 첫째, 질 좋은 난수. 둘째, 그 난수를 편향 없이 항목에 배분하는 알고리즘입니다. 둘 중 하나만 어긋나도 결과가 한쪽으로 쏠립니다.
흔한 실수 1: 정렬로 섞기
배열을 섞을 때 인터넷에서 가장 많이 복사되는 sort(() => Math.random() - 0.5) 한 줄은 사실 고르게 섞이지 않습니다. 정렬 알고리즘은 비교 결과가 항상 일관적이라고 가정합니다. A가 B보다 앞이라고 판정했으면 다시 물어도 같은 답이 나와야 하죠. 그런데 랜덤 비교는 물을 때마다 답이 달라지므로 그 가정을 깨뜨립니다.
가정이 깨지면 정렬 알고리즘은 비교 횟수를 아끼려고 일부 원소 쌍을 아예 비교하지 않고 넘어갑니다. 그 결과 원래 자리 근처에 그대로 남는 항목이 생깁니다. 브라우저마다 쓰는 정렬 알고리즘이 달라서 편향의 방향까지 제각각입니다.
항목이 서너 개일 때는 눈에 잘 안 띄지만, 열 개를 넘어가면 특정 자리에 특정 항목이 유의미하게 자주 배치됩니다. 재미로 한 번 섞는 정도면 상관없지만, 순서나 당첨처럼 결과가 의미를 갖는 자리에서는 쓰면 안 됩니다.
흔한 실수 2: 모듈러 편향
난수를 원하는 범위로 줄일 때 나머지 연산을 쓰는 방식도 미묘한 편향을 만듭니다. 예를 들어 0부터 255 사이의 값을 얻은 뒤 10으로 나눈 나머지를 취해 0부터 9를 만든다고 해봅시다. 256을 10으로 나누면 몫이 25이고 6이 남습니다. 즉 0부터 5까지는 26번씩 나올 기회가 있고, 6부터 9까지는 25번씩만 있습니다.
결과적으로 0부터 5가 나올 확률이 6부터 9보다 약 4% 높아집니다. 항목이 몇 개 안 되고 범위가 크면 차이가 작아 보이지만, 수천 번 추첨하는 자동화 시스템에서는 누적되어 드러납니다.
제대로 하려면 나머지가 생기는 구간의 값을 버리고 다시 뽑는 거부 샘플링(rejection sampling)을 써야 합니다. 이 사이트의 도구들은 범위를 줄일 때 이 방식을 사용해 모든 값이 정확히 같은 확률을 갖도록 합니다.
정석: 피셔-예이츠 셔플
피셔-예이츠(Fisher-Yates) 셔플은 배열 맨 뒤에서부터 하나씩 훑으면서, 아직 섞지 않은 앞쪽 범위 안에서 무작위 위치를 골라 교환하는 방식입니다. 코드로는 열 줄이 안 되지만, 모든 순열이 정확히 같은 확률로 나온다는 것이 수학적으로 증명되어 있습니다.
핵심은 매번 남은 범위 안에서만 위치를 고른다는 점입니다. 실수로 전체 범위에서 고르도록 짜면 순열별 확률이 달라져 편향이 생깁니다. n개 항목의 순열은 n 팩토리얼 가지인데 그렇게 짠 방식의 경우의 수는 n의 n제곱 가지라서, 팩토리얼로 나누어떨어지지 않는 이상 고르게 분배될 수 없습니다.
이 사이트의 랜덤뽑기, 순서뽑기, 팀 나누기는 모두 피셔-예이츠 셔플을 사용합니다. 그래서 항목이 몇 개든 특정 이름이 구조적으로 유리해지는 일이 없습니다.
난수의 품질: 일반 난수와 암호학적 난수
브라우저의 Math.random()은 의사난수 생성기입니다. 내부 상태에서 규칙적으로 다음 값을 계산하기 때문에, 값을 충분히 많이 관찰하면 이론적으로는 다음 값을 예측할 수 있습니다. 대부분의 브라우저는 xorshift128+ 계열 알고리즘을 씁니다.
통계적 품질 자체는 좋습니다. 균등성, 주기, 상관관계 검사를 통과하므로 수업 모둠 나누기나 메뉴 고르기 같은 용도에는 전혀 문제가 없습니다. 문제가 되는 경우는 결과를 미리 알아내려는 동기가 있는 사람이 있을 때입니다.
상금이 걸린 추첨처럼 예측 시도의 이득이 크다면 crypto.getRandomValues()로 만든 암호학적 난수를 써야 합니다. 이 값은 운영체제가 수집한 물리적 잡음을 바탕으로 하며 이전 값에서 다음 값을 역산할 수 없습니다.
가중치를 줄 때도 공정할 수 있다
공정함은 모두 같은 확률만 뜻하지 않습니다. 응모를 세 번 한 사람에게 세 배 확률을 주거나 등급별로 다른 확률을 주는 것도, 규칙이 명확하고 공개되어 있다면 공정합니다. 불공정한 건 확률이 다른 것 자체가 아니라 다른데도 같다고 말하는 상황입니다.
가중치를 줄 때 중요한 건 실제 확률이 설정한 값과 일치하는지입니다. 예를 들어 치킨에 가중치 3을 주고 피자와 초밥을 그대로 두면 가중치 합이 5이므로 치킨 60%, 피자 20%, 초밥 20%가 되어야 합니다. 이 사이트의 도구는 계산된 실제 확률을 화면에 함께 보여주므로, 추첨 전에 참가자에게 그대로 공개할 수 있습니다.
가중치 규칙은 반드시 뽑기 전에 공개하세요. 결과가 나온 뒤에 사실 이 사람은 가중치가 있었다고 설명하면, 규칙이 아무리 타당해도 조작으로 받아들여집니다.
여러 명을 뽑을 때: 중복을 허용할 것인가
한 번에 여러 명을 뽑을 때는 같은 사람이 두 번 뽑힐 수 있는지 먼저 정해야 합니다. 뽑은 항목을 다시 넣지 않는 방식을 비복원추출, 다시 넣는 방식을 복원추출이라고 합니다. 대부분의 추첨은 비복원추출이 자연스럽습니다.
다만 같은 이름을 명단에 두 번 적었다면 그건 서로 다른 두 개의 응모로 취급됩니다. 사람 기준으로 중복을 막고 싶다면 명단 자체에서 중복을 먼저 정리해야 합니다.
여러 라운드로 나눠 뽑는다면 각 라운드 전에 이미 당첨된 이름을 명단에서 빼세요. 이 사이트의 뽑기 도구에는 명단을 그대로 공유 링크로 만드는 기능이 있어서, 정리한 명단을 다음 진행자에게 주소 하나로 넘길 수 있습니다.
결과를 납득시키는 것도 공정함의 일부
알고리즘이 아무리 정확해도 참가자가 믿지 못하면 추첨은 실패합니다. 실무에서 뒷말을 없애는 방법은 대체로 세 가지입니다. 규칙을 미리 공개하기, 명단을 확정하고 그 상태를 보여주기, 뽑는 순간을 여러 사람이 함께 보기.
화면을 공유하면서 명단이 화면에 그대로 보이는 상태로 버튼을 누르는 것만으로도 신뢰도가 크게 올라갑니다. 결과 이미지를 저장해 공지에 첨부하면 나중에 확인 요청이 와도 근거가 남습니다.
반대로 진행자가 혼자 뽑아 결과만 텍스트로 전달하는 방식은 알고리즘과 무관하게 가장 의심받는 형태입니다. 절차의 투명성은 난수의 품질만큼이나 중요합니다.
여기서 바로 사용해 보기
글에서 설명한 내용을 아래에서 바로 확인해 볼 수 있습니다.
처음이라면 여기를 눌러 보세요
- 한 줄에 하나씩 입력합니다.
- 가중치를 쓰려면 이름 뒤에 *숫자를 붙입니다. 예: 민수*3
- 가중치가 클수록 뽑힐 확률이 높아집니다.
가중치를 생략하면 1로 처리합니다. 헷갈리지 않도록 이름*숫자 형식으로 입력하는 것을 권장합니다.
결과
대기 중
?
아직 결과가 없습니다.
전체 기능으로 사용하기
전체 화면, 결과 저장 같은 기능은 도구 페이지에서 모두 쓸 수 있습니다.
자주 묻는 질문
엑셀의 RAND 함수로 뽑아도 되나요?
일상적인 용도로는 충분합니다. 다만 RAND는 시트가 다시 계산될 때마다 값이 바뀌므로, 뽑은 결과를 값으로 붙여넣어 고정해 두어야 나중에 확인할 수 있습니다. 정렬 기준으로 RAND를 쓰는 방식은 동점 처리 방식에 따라 미세한 편향이 생길 수 있습니다.
같은 사람이 계속 뽑히는데 조작 아닌가요?
10명 중에서 뽑을 때 같은 사람이 두 번 연속 뽑힐 확률은 10%입니다. 세 번 연속도 1%로, 100번 중 한 번은 일어납니다. 무작위는 고르게 퍼지지 않고 뭉치는 성질이 있어서, 오히려 지나치게 고르게 돌아가는 결과가 인위적입니다.
명단이 몇 명까지 괜찮나요?
알고리즘 관점에서는 제한이 없습니다. 실제로는 화면에서 확인하기 좋은 규모가 더 중요합니다. 수백 명 단위라면 결과를 이미지로 저장해 근거로 남기는 편이 좋습니다.
뽑기 결과를 나중에 증명하려면 어떻게 하나요?
명단, 규칙, 결과 세 가지를 같은 시점에 남기면 됩니다. 결과 화면을 이미지로 저장하면 명단과 당첨자가 한 장에 함께 담깁니다. 여기에 추첨 일시와 진행자를 함께 공지하면 사후 확인 요청에 대응할 수 있습니다.
입력한 이름이 서버에 저장되나요?
이 사이트의 도구는 모두 브라우저 안에서만 계산합니다. 입력한 명단은 서버로 전송되지 않고 저장되지도 않습니다. 그래서 사내 명단이나 학생 이름을 넣어도 외부에 남지 않습니다.
가중치를 소수로 줄 수도 있나요?
정수로 입력하는 편이 참가자에게 설명하기 쉽습니다. 1.5배를 주고 싶다면 전체를 두 배로 올려 3 대 2로 표현하세요. 확률 자체는 같지만 규칙을 공지할 때 훨씬 명확합니다.