작성일 · · 최종 검토일 · · 6분
한국어 · English
컴퓨터는 어떻게 난수를 만드나
컴퓨터는 정해진 명령을 정해진 순서로 실행하는 기계입니다. 같은 입력을 주면 언제나 같은 결과를 냅니다. 그런 기계가 "무작위"를 만들 수 있을까요? 이 질문의 답은 "만들 수 있는 것처럼 보이게 하는 방법이 두 가지 있고, 두 방법의 성격이 다르다"입니다. 이 글은 의사난수와 암호학적 난수가 무엇인지, 어디서 편향이 생기는지를 설명한 다음, 이 사이트의 번호 생성기와 Lottery Lab이 실제로 코드에서 어떻게 번호를 뽑는지를 있는 그대로 적습니다.
의사난수: 공식으로 만든 난수
가장 오래된 방식은 점화식입니다. 이전 값에서 다음 값을 계산하는 공식을 정해 두고, 처음 값(시드)만 주면 그다음은 모두 공식이 만듭니다. 예를 들어 선형 합동 생성기는 이런 식입니다.
다음 값 = (1664525 × 현재 값 + 1013904223) mod 232
이 상수들은 수치 계산 교재에서 널리 쓰이는 값이고, 이 식은 232개의 값을 한 번씩 모두 지나서야 반복하는 "전체 주기"를 갖는다는 것이 알려져 있습니다(상수가 선형 합동 생성기의 전체 주기 조건을 만족하기 때문입니다). 아래 데모는 이 식에 시드를 넣어 로또 번호 모양의 숫자 6개를 만듭니다. 시드를 바꾸면 다른 숫자가, 시드를 같게 두면 몇 번을 눌러도 같은 숫자가 나오는 것을 확인해 보세요. 이 데모는 중복을 걸러 내지 않으므로 같은 번호가 두 번 나올 수 있습니다.
"같은 시드, 같은 결과"가 의사난수의 가장 중요한 성질입니다. 단점이자 장점입니다. 시뮬레이션이나 테스트에서는 재현할 수 있다는 것이 큰 장점입니다. 이 사이트의 Lottery Lab에 시드 입력칸이 있는 이유도 그래서입니다. 반면 누군가 시드와 공식을 알면 나머지 숫자를 모두 예측할 수 있습니다. 추첨이나 보안에는 이 점이 치명적입니다.
암호학적 난수: 예측할 수 없도록 만든 난수
두 번째 방식은 예측 불가능성을 목표로 합니다. 운영체제는 하드웨어의 잡음, 이벤트의 정확한 타이밍 같은 외부의 불확실성(엔트로피)을 모으고, 이를 시드로 쓰는 생성기를 돌립니다. 이 생성기는 출력 일부를 보더라도 다음 출력을 알아낼 수 없도록 설계되어 있습니다. 브라우저에는 이것을 쓰는 창구가 있습니다. 개발자 문서(MDN)는 crypto.getRandomValues()가 "암호학적으로 강한 난수"를 준다고 설명하면서, 구현이 진짜 난수 생성기가 아니라 충분한 엔트로피로 시딩된 의사난수 생성기라는 점도 적고 있습니다. 알고리즘은 브라우저마다 다를 수 있지만 암호학 용도에 적합하다고 합니다. 같은 문서는 자바스크립트의 Math.random()을 비암호학적 난수원이라고 구분합니다.
그러니 "진짜 무작위"와 "의사난수"의 경계는 생각보다 흐립니다. 암호학적 난수도 내부는 공식입니다. 차이는 시드가 예측 불가능하게 만들어졌는가와, 출력에서 내부 상태를 거꾸로 추적할 수 없게 설계되었는가입니다. 이 사이트는 이 차이를 구분하지만 "진짜 난수"라고 말하지 않습니다.
나머지 연산의 함정: 모듈로 편향
난수를 얻은 뒤에도 문제가 남습니다. 컴퓨터가 주는 값은 0부터 232 − 1 사이의 큰 정수입니다. 1~45 사이 번호를 만들려면 이것을 45로 나눈 나머지에 1을 더하는 방법을 흔히 씁니다. 그런데 이 방법은 미세한 편향을 만듭니다. 값의 개수가 45의 배수가 아니면 일부 번호가 한 번 더 뽑힐 기회를 얻기 때문입니다.
| 난수의 크기 | 값의 개수 | 앞쪽 31개 번호가 나올 경우의 수 | 나머지 14개 번호 | 상대 확률 차이 |
|---|---|---|---|---|
| 8비트 (0~255) | 256 | 6 | 5 | 앞쪽 번호가 20% 더 나옴 |
| 32비트 | 4,294,967,296 | 95,443,718 | 95,443,717 | 약 0.0000001% |
8비트 난수를 쓴다면 1~31번이 32~45번보다 확률이 20% 높게 나옵니다. 이런 편향은 실제로 만들 수 있는 실수입니다. 32비트 난수에서는 차이가 약 1억분의 1로 줄어들어 사실상 보이지 않지만, 편향이 있다는 사실 자체는 사라지지 않습니다. 이를 완전히 없애는 방법이 거부 표본추출입니다. 45의 배수에 맞지 않는 끝부분의 값이 나오면 버리고 다시 뽑습니다. 값의 개수를 45의 배수로 잘라낸 범위 안에서만 나머지를 취하면 모든 번호의 확률이 정확히 같아집니다.
중복 없는 번호: 피셔–예이츠 셔플
로또는 같은 번호가 두 번 나오지 않습니다. 이를 만드는 올바른 방법은 1~45를 늘어놓은 목록을 피셔–예이츠 셔플로 섞어서 앞에서 6개를 가져오는 것입니다. 뒤에서부터 각 위치를 그 위치까지 포함한 앞쪽의 무작위 위치와 바꾸는 방식이며, 모든 순서가 정확히 같은 확률로 나옵니다. 잘못 만든 셔플은 순서에 편향이 생깁니다. 이는 셔플·제비뽑기·사다리타기의 공정성 글에서 시뮬레이터로 직접 확인합니다.
LottoFlick은 실제로 무엇을 쓰나 (코드 기준)
이 절은 이 사이트의 소스 코드를 읽고 쓴 것입니다.
- 번호 생성기 (
js/number-generator.js): 정수를 뽑는 함수는 브라우저의crypto.getRandomValues로 32비트 값을 받고, 범위를 45(또는 해당 게임의 번호 수)로 나눈 나머지를 쓰되 앞에서 설명한 거부 표본추출을 합니다. 범위의 배수로 잘라낸 한계값 이상이 나오면 버리고 다시 뽑습니다. 중복 없는 번호는 후보 목록 전체를 피셔–예이츠 셔플로 섞고 앞에서 필요한 개수를 가져와 만듭니다. - 예외:
crypto를 쓸 수 없는 환경에서는 같은 함수가 자바스크립트의Math.random()으로 대신 뽑습니다. 이 대체 경로에는 위의 거부 표본추출이 없습니다. - 다른 모드: 생성기에는 완전 무작위 외에 생일, 운명 번호, 균형 모드가 있습니다. 이들은 입력된 숫자를 일부 반영하거나 번호가 고르게 분포하도록 조정합니다. 이 모드들은 무작위의 일부를 제한하는 것이어서 균등한 추첨이 아니며, 당첨 확률을 높이지도 않습니다. 어떤 번호든 확률은 같습니다.
- Lottery Lab (
js/lab-engine.js): 시뮬레이션에는 속도를 위해 xoshiro128**라는 비암호학적 생성기를 씁니다. 시드를 입력하지 않으면 브라우저의 안전한 난수원에서 128비트 시드를 받고, 시드를 입력하면 입력한 글자를 해시(cyrb128)해서 시드로 씁니다. 이 경우 같은 시드는 같은 결과를 냅니다. 번호를 뽑는 단계에서는 같은 거부 표본추출과 부분 피셔–예이츠를 씁니다. 시드를 입력한 실행은 결과 요약에 재현 가능하다고, 입력하지 않은 실행은 재현할 수 없다고 기록됩니다.
이 설명은 코드에서 읽은 구조이며 브라우저 안의 crypto가 내부에서 어떤 알고리즘을 쓰는지는 브라우저마다 다릅니다(이 글은 확인하지 않았습니다). 또한 이 사이트는 외부 기관의 난수 인증을 받지 않았습니다.
"무작위인지"는 어떻게 확인하나
난수가 좋은지를 증명하는 것은 불가능에 가깝고, 나쁜지를 가려내는 통계 검사들이 있을 뿐입니다. 번호의 빈도가 고른지, 연속한 값 사이에 상관이 없는지, 순서가 편향되지 않는지를 봅니다. 이 사이트의 핫·콜드 번호 글에서 한 방식, 곧 가짜 이력을 많이 만들어 실제 값과 비교하는 방법도 그 한 가지입니다. 검사를 통과했다는 것은 "그 검사가 찾는 결함이 없다"는 뜻이지 "완벽하게 무작위"라는 뜻이 아닙니다.
난수의 질은 당첨 확률과 관계가 있나
없습니다. 이 사이트의 생성기가 어떤 난수를 쓰든 한 장의 당첨 확률은 실제 추첨기가 뽑는 번호와 비교해 변하지 않습니다. 실제 복권의 당첨 번호는 이 사이트 생성기가 아니라 별도의 추첨 장치에서 나옵니다. 생성기의 난수가 훌륭한 것은 "번호가 쏠림 없이 뽑힌다"는 의미일 뿐 맞힐 가능성을 올린다는 뜻이 아닙니다. 다음 글 추첨은 어떻게 공정성을 보장하나에서 실제 추첨 장치를 다룹니다.
한계
- 코드 설명은 이 글을 쓴 시점의 소스 파일을 읽은 것입니다. 코드가 바뀌면 달라질 수 있습니다.
- 라이브러리나 브라우저의 내부 동작은 확인하지 않았습니다. MDN의 설명은 일반적인 구현에 대한 설명입니다.
- 모듈로 편향의 수치는 단순 나눗셈으로 계산한 값입니다.
- 데모의 공식(선형 합동 생성기)은 설명용이며, 이 사이트의 생성기가 쓰는 것이 아닙니다.
LottoFlick은 독립 사이트이며 복권 운영사가 아닙니다. 당첨 번호는 무작위이고 예측할 수 없습니다.
출처와 확인 상태
- MDN: Crypto.getRandomValues() (충분한 엔트로피로 시딩된 의사난수 생성기에서 나오는 암호학적으로 강한 값, Math.random은 비암호학적) — 확인됨
- LottoFlick 생성기 소스: randomInt(거부 표본추출을 쓰는 안전한 난수원)와 피셔–예이츠 셔플 — LottoFlick 자체 계산
- LottoFlick Lab 엔진 소스: 안전한 난수원 또는 사용자 시드(cyrb128 해시)로 시딩하는 xoshiro128** — LottoFlick 자체 계산