에듀테크 프로젝트 대시보드
의사난수 생성기 (PRNG)
컴퓨터는 사실 진짜 무작위를 만들지 못한다. random() 이 뱉는 값은
완전히 정해진 계산식이 만든 수열일 뿐이다 — 그래서 의사(pseudo) 난수다.
가장 오래된 방법인 선형 합동법(LCG)은 직전 값에 곱하고 더하고 나머지를 취하는
딱 한 줄이다. 같은 시드(seed)로 출발하면 항상 똑같은 수열이 나오고(게임 리플레이·
테스트 재현의 핵심), 값이 유한하므로 언젠가 반드시 반복된다(주기). 게다가 상수를
잘못 고르면 난수쌍을 찍었을 때 격자 무늬(초평면)가 드러나 전혀 무작위로 보이지 않는다.
(a·xₙ + c) mod m 하나로 정해집니다.
이미 나온 값이 다시 등장하는 순간, 그 뒤로는 같은 패턴이 영원히 반복됩니다 (그게 주기).
연속으로 나온 두 값을 좌표 (xₙ, xₙ₊₁) 로 찍어 점을 수천 개 뿌린다.
난수라면 정사각형을 고르게 덮어야 한다. 그런데 곱수 a 를 잘못 고르면
점들이 몇 개의 직선(고차원에서는 초평면) 위에만 놓인다 — 이것이 모든 LCG의 약점인
격자 구조(Marsaglia, 1968)다. RANDU 프리셋은 3차원으로 보면 점이 딱 15개 평면에 갇힌다.
PRNG는 시드만 같으면 몇 번을 다시 돌려도 완전히 동일한 수열을 만든다.
게임의 "시드 입력" 맵 생성, 유닛 테스트의 seed(42), 과학 실험의 재현이 모두 이 성질 덕분이다.
아래 두 실행의 시드를 같게 두면 수열이 포개지고, 다르게 두면 완전히 갈라진다.
# 1) 선형 합동법 (LCG) — glibc/자바 계열이 오래 쓴 방식 class LCG: def __init__(self, seed): self.state = seed self.a, self.c, self.m = 6364136223846793005, 1442695040888963407, 2**64 def next_u01(self): self.state = (self.a * self.state + self.c) % self.m # ★ 딱 한 줄 return self.state / self.m # 0.0 ~ 1.0 # 2) Xorshift — 시프트와 XOR만으로 훨씬 나은 통계 품질 def xorshift32(x): x ^= (x << 13) & 0xFFFFFFFF x ^= (x >> 17) x ^= (x << 5) & 0xFFFFFFFF return x # 주기 2³²−1, 격자 무늬 거의 없음 # 3) 재현성: 시드를 고정하면 언제 돌려도 같은 수열 import random random.seed(42); print([random.randint(1,6) for _ in range(5)]) # [6, 1, 1, 6, 3] random.seed(42); print([random.randint(1,6) for _ in range(5)]) # [6, 1, 1, 6, 3] ← 똑같음
c 와 m 이 서로소, ② m 의 모든 소인수가 a−1 을 나눔, ③ m 이 4의 배수면 a−1 도 4의 배수.m = 2ᵏ LCG는 최하위 비트의 주기가 2밖에 안 된다(0,1,0,1…). rand() % 2 로 동전 던지기 하면 안 되는 이유.a, c 를 역산해 미래 전체를 예측할 수 있다. 보안에는 /dev/urandom·secrets 같은 CSPRNG를 쓴다.| PRNG (LCG·MT·xorshift) | CSPRNG (암호용) | TRNG (하드웨어) | |
|---|---|---|---|
| 근원 | 결정론적 수식 + 시드 | 수식 + 예측 불가 엔트로피 | 물리 잡음(열·양자) |
| 재현 가능 | 예 (시드만 있으면) | 아니오 | 아니오 |
| 속도 | 매우 빠름 | 빠름 | 느림 |
| 예측 가능성 | 출력 보면 역산 가능 | 계산상 불가능 | 불가능 |
| 용도 | 게임·시뮬레이션·셔플·테스트 | 키·토큰·비밀번호 솔트 | CSPRNG 시드·복권 추첨 |