난수 발생 | 컴퓨터가 만드는 랜덤숫자의 진실(Feat.의사 난수) – [高지식] 거니 답을 믿으세요

당신은 주제를 찾고 있습니까 “난수 발생 – 컴퓨터가 만드는 랜덤숫자의 진실(feat.의사 난수) – [高지식] 거니“? 다음 카테고리의 웹사이트 ppa.maxfit.vn 에서 귀하의 모든 질문에 답변해 드립니다: https://ppa.maxfit.vn/blog. 바로 아래에서 답을 찾을 수 있습니다. 작성자 코딩하는거니 이(가) 작성한 기사에는 조회수 69,171회 및 좋아요 1,298개 개의 좋아요가 있습니다.

난수 발생 주제에 대한 동영상 보기

여기에서 이 주제에 대한 비디오를 시청하십시오. 주의 깊게 살펴보고 읽고 있는 내용에 대한 피드백을 제공하세요!

d여기에서 컴퓨터가 만드는 랜덤숫자의 진실(feat.의사 난수) – [高지식] 거니 – 난수 발생 주제에 대한 세부정보를 참조하세요

이번 고지식 열여덟 번째 영상에서는 컴퓨터가 생성하는 랜덤넘버/난수가 어떻게 만들어지는지, 과연 정말 추적이 불가능한 순수한 난수인지? 혹은 하드웨어적으로 더욱 예측이 힘든 난수를 만드는 방법은 무엇인지에 대하여 얘기합니다.
자연에서 정말 쉽게 접할 수 있는 무작위라는 특성이 컴퓨터에는 어떻게 훈련이 되어있는지 확인해보세요!
———————————————————
다른 고지식 시리즈도 시청해보세요! https://tinyurl.com/yxbnlb9e
거니 인스타그램 (근황/미리보기) https://tinyurl.com/yyrfqq5l
페이스북 페이지 https://tinyurl.com/y8t5a5au
——————————————————
#난수 #랜덤 #무작위

난수 발생 주제에 대한 자세한 내용은 여기를 참조하세요.

난수생성 – 나무위키

흔히 보는 랜덤은 정말로 임의의 값이 아니고 특정한 방법으로 계산하거나 몇 밀리초(ms) 단위로 시시각각 변하는 값을 초기값으로 잡고 여러 계산 과정을 …

+ 여기에 더 보기

Source: namu.wiki

Date Published: 7/20/2022

View: 9251

난수 발생기; 랜덤 정수 생성, 무작위 숫자 발생; Random Int …

지정한 범위의 정수로 된 무작위의 랜덤한 숫자들을 만드는 발생기입니다. 기본값은 1에서 100까지의 난수를 20개 생성하는 것인데, 마음대로 변경할 …

+ 여기에 자세히 보기

Source: mwultong.blogspot.com

Date Published: 8/20/2021

View: 6025

난수 발생 함수 – rand – 네이버 블로그

이때 임의로 선택한 값을 난수라고 하며 선택하는 방법은 수학적인 개념(공식)을 사용하여 직접 구현하면 됩니다. 2. rand 함수 난수를 생성하는 공식을 …

+ 여기에 더 보기

Source: m.blog.naver.com

Date Published: 12/7/2021

View: 9869

난수발생기 – 위키백과, 우리 모두의 백과사전

난수발생기(亂數發生機, 영어: random number generator, RNG) 또는 난수생성기(亂數生成機)는 무작위성 기회보다 이론적으로 예측을 더 할 수 없도록 일련의 숫자나 …

+ 여기에 더 보기

Source: ko.wikipedia.org

Date Published: 12/25/2021

View: 8024

C 랜덤 – 난수 생성하기 – 열코의 프로그래밍 일기

해석하면 rand() 함수에 의해 난수를 생성하고 그 숫자를 9로 나눈 나머지를 random 변수에 대입하는 코드입니다. ※ 알고가기.

+ 더 읽기

Source: yeolco.tistory.com

Date Published: 12/24/2021

View: 4941

3.5 난수 발생과 카운팅 – 데이터 사이언스 스쿨

이렇게 시드를 설정한 후 넘파이 random 서브패키지에 있는 rand 함수로 5개의 난수를 생성해 보자. rand 함수는 0과 1사이의 난수를 발생시키는 함수로 인수로 받은 …

+ 여기에 자세히 보기

Source: datascienceschool.net

Date Published: 4/21/2021

View: 1201

컴퓨터가 만드는 랜덤은 정말로 랜덤할까? – Evan Moon

위에서 설명했듯이 무작위 로 발생하는 어떠한 패턴이다. 사람마다 의견이 분분하겠지만 지금 필자 머리 속에 떠오른 대표적인 랜덤은 바로 도박 이다.

+ 여기를 클릭

Source: evan-moon.github.io

Date Published: 6/17/2021

View: 3151

[Javascript] 난수 생성하기 (랜덤 숫자, Random Number)

Javascript로 난수를 생성하는 방법을 소개합니다. Math.random() 범위를 지정한 난수 생성하기 0~9 0~10 0~99 0~100 1~10 2~5 난수 생성 함수 만들기 …

+ 여기에 보기

Source: hianna.tistory.com

Date Published: 12/25/2021

View: 6214

3. 난수 생성()과 시간 관련 라이브러리(

이번 강좌에서는 C++ 11 에 추가된 난수(Random number)를 쉽게 생성할 수 있도록 도와주는 라이브러리와 시간 관련 데이터를 다룰 수 있게 도와 …

+ 여기에 더 보기

Source: modoocode.com

Date Published: 9/30/2021

View: 517

난수 발생 – Apple 지원 (KR)

난수 발생. CPRNG(의사 난수 발생기)는 보안 소프트웨어의 중요한 구성 요소입니다. 이를 위해 Apple은 iOS, iPadOS, macOS, tvOS 및 watchOS 커널에서 실행되는 신뢰 …

+ 여기에 표시

Source: support.apple.com

Date Published: 7/11/2021

View: 149

주제와 관련된 이미지 난수 발생

주제와 관련된 더 많은 사진을 참조하십시오 컴퓨터가 만드는 랜덤숫자의 진실(feat.의사 난수) – [高지식] 거니. 댓글에서 더 많은 관련 이미지를 보거나 필요한 경우 더 많은 관련 기사를 볼 수 있습니다.

컴퓨터가 만드는 랜덤숫자의 진실(feat.의사 난수)  -  [高지식] 거니
컴퓨터가 만드는 랜덤숫자의 진실(feat.의사 난수) – [高지식] 거니

주제에 대한 기사 평가 난수 발생

  • Author: 코딩하는거니
  • Views: 조회수 69,171회
  • Likes: 좋아요 1,298개
  • Date Published: 2019. 1. 31.
  • Video Url link: https://www.youtube.com/watch?v=dzzryM3TtHk

난수 발생기; 랜덤 정수 생성, 무작위 숫자 발생; Random Int Number Generator

지정한 범위의 정수로 된 무작위의 랜덤한 숫자들을 만드는 발생기입니다. 기본값은 1에서 100까지의 난수를 20개 생성하는 것인데, 마음대로 변경할 수 있습니다. 예를 들어 0에서 시작할 수도 있고, 음수인 마이너스 10 (-10)부터 시작할 수도 있습니다.

에서 까지의 난수, 개 생성

난수 개수를 2000개 이상 지정하면, 확인 대화상자 에서까지의 난수,개 생성난수 개수를 2000개 이상 지정하면, 확인 대화상자

범위로 지정한 숫자들도 난수에 포함됩니다. 가령 1에서 100까지를 지정했을 때, 시작 범위 숫자인 1과 끝 범위 숫자인 100도, 발생된 난수에 포함 가능합니다.

정수만 지정 가능합니다. 만약 소수점 이하가 있는 실수를 지정하면 소수점 이하는 무시되고 정수화됩니다.

▶▶

▶▶

더 읽기:

난수 발생 함수 – rand

실행 결과 :

11451, 11451, 11451, 11451, 11451, 11451, 11451, 11451, 11451, 11451, 11451, 11451, 11451, 11451, 11451, 11451, 11451, 11451, 11451, 11451,

따라서 srand는 rand 함수가 반복적으로 사용되더라도 발생을 시작하는 시점에 한 번만 호출하는 것이 좋습니다. 그리고 srand 함수가 적용되는 단위가 스레드(thread)이기 때문에 멀티 스레드를 사용하는 프로그램이라면 스레드마다 srand를 개별적으로 사용하는 것이 좋습니다.

4. rand 함수의 활용

rand 함수를 사용하다 보면 의문이 생길 수 있습니다. 자신은 0에서 10사이의 숫자를 난수로 사용하고 싶은데 rand 함수가 발생하는 값이 0에서 32767사이의 값이기 때문에 난수의 범위가 맞지 않다는 점입니다. 하지만 이런 의문은 간단한 수학 연산으로 해결할 수 있습니다.

아래와 같이 % 연산자를 사용하여 난수 값을 11로 나눈 나머지를 사용하게 되면 rand 함수가 어떤 값을 발생시키든지 number에는 0에서 10사이의 숫자만 대입됩니다.

위키백과, 우리 모두의 백과사전

주사위 는 기계식 하드웨어 난수 발생기의 한 예이다.

난수발생기(亂數發生機, 영어: random number generator, RNG) 또는 난수생성기(亂數生成機)는 무작위성 기회보다 이론적으로 예측을 더 할 수 없도록 일련의 숫자나 심볼을 생성하는 장치이다. 난수발생기는 진정한 난수를 생성하는 진정한 하드웨어 난수발생기(HRNG) 또는 무작위의 숫자를 생성하지만 실제로 결정적이면서 PRNG의 상태를 모르는 경우 재현이 가능한 유사난수 발생기(PRNG)일 수 있다.

같이 보기 [ 편집 ]

난수 생성하기

C언어 프로그래밍에서 코드를 작성하다보면 무작위의 숫자가 필요한 경우가 존재합니다.

보통 무작위 숫자를 난수라고 표현합니다.

C언어에서 난수를 표현하기 위해서는 라이브러리를 사용해야합니다.

※ 알고가기

라이브러리란 자주 사용하는 함수들을 미리 작성하여 저장해둔 파일로써 보통 헤더파일로 저장됩니다. 이 헤더파일은 #include 라는 코드를 통해 추가해 줄 수 있는데 C언어에서 기본적으로 사용하는 헤더파일은 stdio.h 이며 이는 Standard Input Ouput(표준 입출력)의 약자입니다. 사용방법은 프로그램 코드 맨 윗줄에 #include <헤더파일명.h> 또는 “헤더파일명.h” 으로 사용할 수 있습니다. 보통 < > 는 Standard Library Header에서 사용하며 ” “는 User Defined Header를 사용할 때 쓰는것이라 알려져있는데 크게 틀린말은 아닙니다. < >와 ” “의 차이는 – < > : 컴파일러가 미리 정해놓은 위치에서 헤더파일을 찾습니다. – ” ” : 컴파일러가 미리 정해놓은 위치에서 헤더파일을 찾고 만약 찾지 못한다면, < >로 바꾸어 헤더파일을 찾습니다. 로 생각하면 되겠습니다. 결국 Header파일을 읽은 것인지 Source파일을 읽을 것인지에 따라 사용을 구분하면 되겠습니다.

다시 본론으로 와서, C언어에서 난수를 만들기위해서는 rand()라는 함수를 사용하면 됩니다.

rand()함수는 stdlib.h 헤더파일에 포함되어있기 때문에 코드 맨 윗줄에 #include 를 작성해 줍니다.

☞ rand()함수 사용법

#include #include // rand() 함수 포함 라이브러리

int main() { int random = 0; // 정수형 변수 선언 for (int i = 0; i < 10; i++) { // 10번 반복 random = rand()%9; // 난수 생성 printf("%d ", random); // 출력 } } ☞ 실행결과 8번째 줄을 보시면 random = rand()%9; 라는 코드가 있습니다. 해석하면 rand() 함수에 의해 난수를 생성하고 그 숫자를 9로 나눈 나머지를 random 변수에 대입하는 코드입니다. ※ 알고가기 rand() 함수에 의해 생성되는 난수 : 0 ~ 32767 rand() 함수에 의해 생성된 난수를 9로 나눈 나머지(%)의 값은 0부터 8까지입니다. 이를 1부터 9까지 난수를 생성하고 싶을때는 다음과 같이 8번째 줄 코드를 변경합니다. random = rand()%9 + 1; 이는 0부터 8까지 반환되는 난수에 1을 더해줌으로써 1부터 9까지 반환할 수 있도록 하는 코드입니다. 하지만 여기서 문제점이 발생합니다. 분명 개발자가 원하는 난수를 생성하고 출력했지만 그 패턴(규칙)이 매번 일정하다는 겁니다. 다시말해, 프로그램 실행 시(또는 반복문) 항상 같은 난수가 생성된다는 겁니다. 이는 진정한 난수라고 말할 수 없죠. 그렇다면 프로그램 실행 시 매번 다르게 난수를 생성하는 방법은 무엇일까요? 그건 바로 srand()라는 함수를 사용하면 됩니다! rand() 함수를 사용하기 전에 다음과 같은 코드를 추가합니다. srand(time(NULL)); 무슨뜻이냐면 srand()함수는 rand()라는 함수에 무작위의 시드값을 주기위한 함수이며 그 파라미터로 time(NULL)이라는 매개변수를 전달합니다. time(NULL)은 1970년 1월 1일 이후 경과된 시간을 초 단위로 반환하는 함수입니다. 이로써 1초 단위로 매번 다른 시드값을 생성해 rand()함수를 호출하는 것입니다. 이해가 안가시는분은 그냥 이걸 써야 진정한 난수가 생성되는구나! 라고 생각하시면 됩니다. 물론 time()함수를 사용하기 위해서 #include 를 추가해주시는걸 잊지마세요!

☞ 진정한 난수 생성코드

#include #include // rand()함수 포함 라이브러리 #include // time()함수 포함 라이브러리

int main() { srand(time(NULL)); // 매번 다른 시드값 생성 int random = 0; // 정수형 변수 선언 for (int i = 0; i < 10; i++) { // 10번 반복 random = rand()%9; // 난수 생성 printf("%d ", random); // 출력 } } ☞ 실행결과 3번 실행 한 결과 모두 다른 난수값이 생성되는것을 확인 할 수 있습니다! 이로써 진정한 난수 생성하는 방법 및 코드에 대해 알아보았습니다. 정보가 유익하셨다면 아래 공감버튼 눌러주시면 감사하겠습니다. 질문사항은 댓글로 달아주시면 성의껏 답변해드리겠습니다.

컴퓨터가 만드는 랜덤은 정말로 랜덤할까?

이번 포스팅에서는 랜덤 에 대해서 한번 이야기 해볼까 한다. 랜덤이란 어떤 사건이 발생했을 때 이전 사건과 다음 사건의 규칙성이 보이지 않는, 말 그대로 무작위 로 발생하는 패턴을 이야기한다. 우리가 사용하고 있는 컴퓨터도 랜덤한 패턴을 만들어야 할 때가 있고 또 실제로도 만들고 있다.

하지만 컴퓨터는 사실 그냥 기능이 많은 계산기 에 불과하다. 계산기는 입력된 값을 가지고 이리 저리 가지고 놀다가 결과값을 내놓는 물건이다. 근데 이런 계산기가 어떻게 랜덤 한 결과를 만들어낼 수 있는 것일까? 우리는 이 질문에 대한 답을 찾기 전에 근본적으로 랜덤이란 것이 무엇인지부터 생각해봐야한다. 진짜 무작위 라는 것이 존재하기는 하는 걸까?

먼저, 랜덤 이란 무엇일까? 위에서 설명했듯이 무작위 로 발생하는 어떠한 패턴이다.

사람마다 의견이 분분하겠지만 지금 필자 머리 속에 떠오른 대표적인 랜덤은 바로 도박 이다. 도박의 가장 위험한 점이 비록 이번 판에는 잃었지만 다음 판에는 나도 딸 수 있을거야! 라는 희망인데, 이런 희망은 도박할 때 사용하는 게임들이 랜덤 에서 기반하는 게임이라는 생각에서 출발하기 때문이다. 즉, 어느 정도 운빨게임이어야 한다는 것이다.

대법원 판례 2006도736에도 도박의 정의를 재물을 걸고 우연에 의하여 재물의 득실을 결정하는 것 이라고 이야기하고 있다. 대표적인 도박인 파칭코, 섰다, 포커, 주식 등만 살펴봐도 대충 감이 온다.

필자는 도박에서 사용하는 게임을 잘 모르기 때문에 누구나 해봤을 법한 가벼운 도박을 예로 들어보겠다. 필자가 중고등학생 시절을 보낸 2000년대에 전국의 중, 고등학교에서 널리 행해졌던 놀이인 판치기 이다. 판치기는 워낙 전국적으로 유행했기 때문에 필자 또래의 독자분들이면 독자분들이면 한번 쯤은 해봤거나 아니면 친구들이 하는 걸 보기는 했을 거라 믿는다.

판치기하다가 선생님들한테 걸리면 교무실로 끌려가서 바로 빠따행이었다.

그래도 혹시 판치기가 뭔지 모르는 분들이 있을 수 있으니 일단 간단하게 룰을 설명하겠다.

적당히 두꺼운 교과서를 준비한다. 국사책이나 물리책처럼 적당히 두꺼운 책을 사용하자. 각자 준비한 동전을 교과서에 올린다. 보통 100원을 건다. 순서대로 교과서를 손으로 때려서 동전을 뒤집는다. 모든 동전을 뒤집어서 같은 면으로 만든 사람이 판돈을 모두 가져간다.

상식적으로 교과서를 손으로 때렸을 때 n 개의 동전이 뒤집어 질 것이라는 계산을 하기란 쉽지 않다. 즉, 판치기는 어느 정도 랜덤 에 기반한 게임이고 그래서 도박 의 기본적인 특성인 사행성 을 가질 수 있는 것이다.

참고로 형법 제 246조 1항과 국내 도박법 판례 상 학교에서 하는 판돈이 몇백원 정도인 판치기는 일시 오락으로 판정받아서 무죄이지만, 아무리 판치기라고 해도 몇백만원 수준의 돈이 왔다갔다 하는 수준이면 도박죄로 처벌받을 수 있다는 점 알아두자. (물론 저 정도 돈이 있는 사람들이면 판치기말고 다른 걸 한다…)

그럼 이 쯤에서 처음의 그 질문을 한번 던져보겠다.

이게 정말로 랜덤일까?

예전에 필자의 학창시절을 생각해봐도 학교에 1~2명씩 판치기의 절대 고수들이 있었는데 이 친구들은 자신이 원하는 동전만 정확히 뒤집을 수 있는 능력을 보유하고 있었다. 예전에 TV에서도 판치기의 고수라고 나온 분이 있었는데 이 분은 뭐 거의 닥터 스트레인지 수준이었다.

사실 판치기도 결국은 물리 법칙 내에서 돌아가는 판이기 때문에 판돈으로 걸린 동전의 무게, 판을 내려치는 힘, 판으로 사용된 교과서의 탄력성 등 몇가지 변수를 알면 어느 동전이 뒤집어 질지도 계산할 수 있을 것이다. 물론 이렇게 계산하는 것이 쉬운 일은 아니기 때문에 우리는 진정한 의미의 랜덤 이 아니더라도 이 정도면 그냥 랜덤 하다고 치는 것이다.

즉, 우리 주변에서도 진정한 의미의 랜덤은 그렇게 많지 않다는 것을 알 수 있다. 단지 우리가 랜덤하다고 생각할 뿐이다.

컴퓨터에서의 랜덤

사실 우리가 일상에서 어느 정도 우연성이 있다면 랜덤하다고 하듯이 컴퓨터도 진정한 의미의 랜덤을 만들어 내는 것이 아니라 어느 정도 납득할만한 우연성을 만들어 내고 이를 랜덤하다고 한다. 게다가 컴퓨터로 뭔가를 만드려면 어떠한 규칙을 만들어줘야 하는데, 어떠한 규칙으로 규칙이 없는 랜덤을 생성한다는 말 자체가 모순이다.

그래서 우리는 랜덤은 아니지만 랜덤에 가까운 유사 랜덤(Pseudo Random) 밖에 만들 수 밖에 없는 것이다. 또한 컴퓨터는 기본적으로 숫자를 기반으로 하는 일종의 계산기이기 때문에 랜덤한 사건을 만들기 위해서는 난수 , 즉 랜덤한 수를 뽑아 낼 수 있어야 한다.

그럼 컴퓨터는 어떻게 이런 난수 생성을 하는 걸까?

중앙제곱법(Mid Square Method)

중앙제곱법 은 폰 노이만이 1949년에 고안한 유사 난수 생성법으로, 임의의 숫자를 제곱한 다음 이 숫자의 일부분을 가져와서 새로운 난수로 만들어내는 방법이다.

임의의 4자리 난수를 만든다고 가정해보자. 초기 값은 심플하게 1234 로 가겠다.

페이즈 대상값 제곱값 난수 0 1234 1522756 1522756 1 5227 27321529 27321529 2 3215 10336225 10336225

이런 식으로 계속 중앙에 있는 값을 빼와서 제곱하면서 임의의 난수를 생성하는 방법이 바로 중앙제곱법 이다. 뭔가 딱봐도 예측하기 쉬워보인다. 아무래도 폰 노이만 형이 활동하던 1950년대에 개발된 알고리즘이고, 그 당시 컴퓨터의 성능은 눈물나기 그지 없었으므로 최대한 간단한 방법을 사용한 것이다. 그래서 이 방법은 요즘에는 거의 사용되지 않는다.

선형합동법(Linear Congruential Method)

선형합동법 은 C의 rand 함수에서 사용하는 알고리즘이며 다음과 같은 재귀 관계식으로 정의되며 난수들의 수열을 반환한다.

X n + 1 = ( a X n + c ) m o d m X_{n+1} = (aX_n + c) \mod m X n + 1 ​ = ( a X n ​ + c ) m o d m

여기서 X X X는 난수로 된 수열이고 나머지는 그냥 임의의 정수이다. 참고로 ANSI C 표준은 m = 2 31 , a = 1103515245 , c = 12345 m = 2^{31}, a = 1103515245, c = 12345 m=231,a=1103515245,c=12345로 정해져있다. 그럼 간단하게 한번 자바스크립트를 사용해서 구현해보자.

let m = 2 ** 31 ; const a = 1103515245 ; const c = 12345 ; function rand ( x ) { return ( ( ( x * a ) + c ) % m ) ; } function getRandomNumbers ( randCount = 1 ) { const initial = new Date ( ) . getTime ( ) ; const randomNumbers = [ initial ] ; for ( let i = 0 ; i < randCount ; i ++ ) { randomNumbers . push ( rand ( randomNumbers [ i ] ) ) ; } randomNumbers . shift ( ) ; return randomNumbers ; } console . log ( getRandomNumbers ( 10 ) ) ; 코드를 브라우저 콘솔에 붙혀넣고 실행시켜보면 인자로 주었던 10만큼의 길이를 가진 난수 배열이 출력된다. ( 10 ) [ 1163074432 , 465823232 , 1719475776 , 1744670976 , 790949120 , 552540416 , 896259328 , 1473241344 , 1074855168 , 575793408 ] 선형합동법 의 특징은 이전에 생성된 난수를 활용한다는 것이며 최대 m m m만큼 경우의 수를 가지므로 최악의 경우 m m m 만큼의 반복 주기를 가진다. 필자가 m 변수를 let 키워드로 선언한 건 이 이유다. 한번 브라우저에서 m 변수에 작은 수를 할당한 다음에 getRandomNumbers 함수를 다시 호출해보자. 난수의 경우의 수와 동일한 값이 출현이 눈에 띄게 커진다는 것을 확인해볼 수 있다. 선형합동법은 계산이 굉장히 간단하고 빠르기 때문에 초창기부터 컴퓨터에 널리 사용되었다. 그러나 선형합동법 은 난수에 주기성이 있고 생성되어 나오는 난수들 사이에 상관 관계가 존재하기 때문에 마지막으로 생성된 난수와 그 외 변수들만 알면 그 다음에 생성될 난수를 모두 예측할 수 있다. 문제는 그 변수들이 ANSI C 표준으로 정해져 있어서 누구든지 다 알 수 있다는 점이다. 즉, 조금 지식이 있는 사람이면 rand 함수의 결과를 보고 다음 난수를 미리 예상할 수 있다는 것이다. 그래서 이 알고리즘은 난수가 예측당해도 상관없는 경우나 임베디드처럼 메모리를 많이 사용하지 못하는 제한된 상황에서 주로 사용한다. 메르센 트위스터(Mersenne Twister) 메르센 트위스터 는 엑셀, MATLAB, PHP, Python, R, C++ 등에서 사용하고 있는 난수 생성 알고리즘이며, 1997년에 마츠모토 마코토 와 니시무라 다쿠지 가 개발한 알고리즘이다. 메르센 트위스터라는 이름은 이 알고리즘의 난수 반복 주기가 메르센 소수 인데서 유래했다. 메르센 소수라고 하면 뭔가 대단한 수 같은데 사실 별 거 없다. 메르센 수 는 M n = 2 n − 1 M_n = 2^{n} - 1 Mn​=2n−1으로 나타내며 식 그대로 2의 n제곱에서 1이 모자란 수 를 말하는 것이고 메르센 소수 는 그냥 이 메르센 수 중에서 소수인 것을 고른 것이다. 보통 2 19937 − 1 2^{19937} - 1 219937−1의 난수 반복 주기를 가지는 MT19937 이 많이 사용되는데, C++에서도 이 알고리즘을 채택해서 사용하고 있다. 이 알고리즘의 동작 원리를 간단하게 설명하면 다음과 같다. seed를 사용하여 624 만큼의 길이를 가진 벡터를 생성. seed는 보통 하드웨어 노이즈나 오늘 날짜를 사용한다. 이 벡터를 사용하여 624개의 유사 난수를 만든다. 이 벡터에 노이즈를 준 후 다시 2번을 반복. 이 노이즈를 주는 행위를 Twist한다고 한다. 이때 3번의 Twist 하는 과정에서 GFSR(Generalized Feedback Shift Register 라는 방법을 사용한다. GFSR 은 자료가 많지 않고 대부분 논문같은 학술 자료만 있는 상황이라 필자가 자세히 알아보지는 못했으나, 열심히 구글링하고 논문들을 뜯어본 결과 LFSR(Linear Feedback Shift Register) 를 약간 변형한 방법이라는 정보를 얻을 수 있었다. (정보가 너무 제한적이라 LFSR 기반이 맞는지는 정확하지 않다.) LFSR 은 이전 상태 값들을 가져와서 선형 함수를 통해 계산한 후 그걸 사용해서 다음 값을 만들어 내는 방법이다. 이때 사용하는 함수는 보통 XOR 를 많이 사용하고 맨 처음 값을 시드(Seed) 라고 부른다. LFSR 를 간단히 설명하자면, 우선 몇개의 메모리 주소를 골라놓고 초기화된 인풋인 시드를 레지스터에 밀어넣는다. 그러면 오른쪽으로 한칸씩 비트가 밀리게(Shifting) 된다. 그러면 우리는 끝에서 삐져나온 한개의 비트를 아웃풋에서 얻게 된다. 그 다음 미리 골라놨던 메모리 주소에 접근해서 값을 빼온 다음에 순서대로 하단에 위치한 3개의 XOR 게이트에 통과시키면 다음 인풋이 나오고 그걸 또 레지스터에 밀어넣는걸 반복하는 것이다. 메르센 트위스터도 결국은 LFSR 가 약간 변형된 GFSR 를 사용하여 난수를 생성하기 때문에 초반에 시드를 생성해줘야한다. 메르센 트위스터의 자세한 알고리즘은 위키피디아의 Mersenne Twister - Algorithmic_detail에서 확인할 수 있다. 필자는 2002년에 마츠모토 마코토 와 니시무라 다쿠지 가 자신들의 메르센 트위스터 알고리즘을 개선해서 다시 작성한 C의 MT19937 알고리즘 코드를 보고 자바스크립트로 한번 포팅해보았다. 이 코드는 최대 32bit 길이의 난수를 사용하도록 작성되어있다. 64bit용 알고리즘은 MT19937-64 라고 또 따로 있다. const N = 624 ; const M = 397 ; const F = 1812433253 ; const UPPER_MASK = ( 2 ** 32 ) / 2 ; const LOWER_MASK = UPPER_MASK - 1 ; const MATRIX_A = 0x9908b0df ; class MersenneTwister { constructor ( ) { const initSeed = new Date ( ) . getTime ( ) ; this . mt = new Array ( N ) ; this . index = N + 1 ; this . seedMt ( initSeed ) ; } seedMt ( seed ) { let s ; this . mt [ 0 ] = seed >>> 0 ; for ( this . index = 1 ; this . index < N ; this . index ++ ) { this . mt [ this . index ] = F * ( this . mt [ this . index - 1 ] ^ ( this . mt [ this . index - 1 ] ) >>> 30 ) + this . index ; this . mt [ this . index ] &= 0xffffffff ; } } int ( ) { let y ; const mag01 = new Array ( 0 , MATRIX_A ) ; if ( this . index >= N ) { let kk ; if ( this . index === N + 1 ) { this . seedMt ( 5489 ) ; } for ( kk = 0 ; kk < N - M ; kk ++ ) { y = ( this . mt [ kk ] & UPPER_MASK ) | ( this . mt [ kk + 1 ] & LOWER_MASK ) ; this . mt [ kk ] = this . mt [ kk + M ] ^ ( y >>> 1 ) ^ mag01 [ y & 1 ] ; } for ( ; kk < N - 1 ; kk ++ ) { y = ( this . mt [ kk ] & UPPER_MASK ) | ( this . mt [ kk + 1 ] & LOWER_MASK ) ; this . mt [ kk ] = this . mt [ kk + ( M - N ) ] ^ ( y >>> 1 ) ^ mag01 [ y & 1 ] ; } y = ( this . mt [ N – 1 ] & UPPER_MASK ) | ( this . mt [ 0 ] & LOWER_MASK ) ; this . mt [ N – 1 ] = this . mt [ M – 1 ] ^ ( y >>> 1 ) ^ mag01 [ y & 1 ] ; this . index = 0 ; } y = this . mt [ this . index ++ ] ; y ^= ( y >>> 11 ) ; y ^= ( y << 7 ) & 0x9d2c5680 ; y ^= ( y << 15 ) & 0xefc60000 ; y ^= ( y >>> 18 ) ; return y >>> 0 ; } ; }

코드 내부에 사용된 상수 F = 1812433253 , MATRIX_A = 0x9908b0df , this.seedMt(5489) 등은 필자가 임의로 넣은 수가 아니라 MT19937 의 표준 계수로 정해져 있는 수이다.

사실 알고리즘 자체가 너무 복잡해서 필자도 다 이해하지는 못했다. (대학 때 공부를 열심히 안한 죄…) 그래도 일단 이렇게 코드로 한번 직접 작성해보면 글로만 읽을 때보다는 확실히 조금 더 이해가 되기 때문에 C로 작성된 원래 코드를 자바스크립트로 포팅한 것이다. 필자가 참고한 C 라이브러리 코드는 여기서 확인할 수 있다.

const rand = new MersenneTwister ( ) ; console . log ( rand . int ( ) ) ;

작성한 코드를 직접 실행해보니 랜덤한 난수가 잘 생성이 되는 것 같다. 사실 난수가 잘 생성되는지 정확하게 보려면 결과를 시각화한 후 난수의 분포도를 봐야하지만 귀찮은 관계로 일단 패스하겠다. 참고로 이 알고리즘을 만든 마츠모토 마코토의 홈페이지에 들어가면 이 알고리즘이 왜 이런 이름을 가지게 되었는지 나와있다.

마코토: Knuth 교수님이 이 이름 발음하기 너무 힘들대.

다쿠지: … [며칠 후] 마코토: 하이 타쿤, 메르센 트위스터는 어때? 메르센 소수를 사용하기도 했고, 원형은 Twisted GFSR(Generalized Feedback Shift Register)니까.

다쿠지: 글쎄…?

마코토: 왠지 제트코스터 같이 들리는 걸? 빨라보이기도 하고 기억하고 쉽고 발음하기 좋고. 그리고 이건 비밀인데 이 이름엔 우리들의 이니셜이 숨어있지!(Makoto, Takuji)

다쿠지: …

마코토: 자자자 MT로 가는거다?

다쿠지: 음…그래 나중에 우리는 Knuth 교수님으로부터 “좋은 이름인 것 같다”라는 편지를 받았다.

뭔가 깨발랄한 마코토형과 시니컬한 다쿠지형 케미가 돋보인다. 뭔가 이런 대단한 알고리즘을 개발한 사람이지만 사람 냄새나는 네이밍 과정인듯.

XOR 시프트(XOR shift)

XOR 시프트 도 메르센 트위스터와 마찬가지로 Linear Feedback Shift Register 을 기반으로 한 의사 난수 알고리즘이다. 어떻게 보면 메르센 트위스트의 하위호환이라고 볼 수도 있는데, 메르센 트위스트와 원리는 비슷하지만 구현이 훨씬 간단하고 작동이 빠르기 때문에 왕왕 사용된다.

XOR 시프트 알고리즘은 여러 종류가 있는데, 32bit , 64bit , 128bit 의 수를 사용하며 각각 2 32 − 1 2^{32} -1 232−1, 2 64 − 1 2^{64} – 1 264−1, 2 128 − 1 2^{128} – 1 2128−1의 메르센 수 난수 반복 주기를 가진다.

근데 이게 TestU01 이라는 난수 품질 테스트 프레임워크의 테스트를 통과하지 못해서 조금 문제가 있었다. 이걸 통과해야 ANSI C 표준이 될 수 있기 때문이다.

그래서 몇가지 변종 알고리즘이 나오게 되었는데 그 중 하나가 128bit 를 사용하는 XOR 시프트 128 을 개량한 XOR 시프트 128 + 라는 알고리즘이다. (뭔가 게임 후속작 제목 같다…)

참고로 V8 , SpdierMonkey , JavaScriptCore 등 메이저 자바스크립트 엔진들이 채택한 난수 생성 알고리즘이 XOR 시프트 128 + 이다. 이 알고리즘은 2016년 11월에 Sebastiano Vigna 라는 분에 의해 학회에서 발표되었다. 논문 내용은 여기에서 확인할 수 있다.

전반적인 원리는 위에서 설명한 LFSR 와 비슷하니, V8 엔진에서 Math.random 메소드를 어떻게 구현했는지 한번 살펴보자. 원래 코드는 C++로 작성되어있으나 여러분이 브라우저에서 코드를 쉽게 실행시켜볼 수 있도록 동일한 로직을 자바스크립트로 포팅해서 작성하겠다.

V8 내부 소스 코드는 v8/src/base/utils/random-number-generator.h 의 XorShift128 메소드에 작성되어 있다.

const state = [ 1827981275 , 1295982171 ] ; function xorshift128plus ( ) { let s1 = state [ 0 ] ; let s0 = state [ 1 ] ; state [ 0 ] = s0 ; s1 ^= s1 << 23 ; s1 ^= s1 >> 17 ; s1 ^= s0 ; state [ 1 ] = s1 ; console . log ( ‘레지스터의 현재 상태 -> ‘ , state ) ; return state [ 0 ] + state [ 1 ] ; } console . log ( ‘초기 레지스터 상태 -> ‘ , state ) ; for ( let i = 0 ; i < 5 ; i ++ ) { console . log ( '난수 -> ‘ , xorshift128plus ( ) ) ; }

초기 레지스터 상태 – > [ 1827981275 , 1295982171 ] 레지스터의 현재 상태 – > [ 1295982171 , 867440954 ] 난수 – > 2163423125 레지스터의 현재 상태 – > [ 867440954 , 1393243966 ] 난수 – > 2260684920 레지스터의 현재 상태 – > [ 1393243966 , 37812574 ] 난수 – > 1431056540 레지스터의 현재 상태 – > [ 37812574 , 833890405 ] 난수 – > 871702979 레지스터의 현재 상태 – > [ 833890405 , 1661667227 ] 난수 – > 2495557632

출력된 로그를 보면 레지스터의 상태가 어떻게 변하고 있는지 알 수 있다.

[1] 번 인덱스에 있던 값이 [0] 번 인덱스로 옮겨지고 [1] 번 인덱스에 새로운 난수를 꽂아넣은 후 두 원소를 더해서 출력하고 있다는 걸 알 수 있다.

계산 과정을 보면 [1] 번 인덱스에 새로운 값을 넣을 때는 시프팅되어 왼쪽으로 삐져나온 값인 [0] 번 인덱스의 값을 사용하여 XOR 시프팅 을 진행하고 그 값을 다시 [1] 번 인덱스에 넣어준다.

그리고 시프팅하는 상수인 23 , 17 은 XOR 시프팅 128+ 알고리즘을 개발할 때 연구를 통해 찾아낸 최적의 상수이기 때문에 사용하는 것이다. 해당 논문을 보면 뭐 이것저것 시도해본 다음에 결과를 일일히 테스트해서 나온 결과를 비교한 도표도 함께 첨부되어있다.

마치며

사실 처음에는 가벼운 마음으로 랜덤 에 대한 이야기를 해보려고 했는데 예상보다 수학적인 내용이 많이 나와서 왠지 어려운 포스팅이 되어버린 것 같다.

사실 컴퓨터라는 계산기로 진정한 의미의 난수를 만드는 것은 거의 불가능하다. 최근에 미국에서 양자컴퓨터를 사용해서 진정한 의미의 난수를 생성하는 데 성공했다고 하지만 양자컴퓨터는 아직은 우리와 너무나도 먼 이야기이기 때문에 논외로 치겠다.

필자도 사실 수학을 그다지 좋아하는 편은 아니다. 하지만 이렇게 수학적인 연구를 통해 생성 규칙이 없는 난수 를 만드려는 많은 사람들의 도전을 보면, 편하게 상위 레이어에서 코딩하고 있는 필자로써는 이 분들에게 굉장히 감사함을 느낀다. (난 안될꺼야 아마…)

연구원 분들이 기반 알고리즘을 만들어주시면 열심히 상용 어플리케이션에 써먹겠습니다…!!!

필자는 이 포스팅을 작성하면서 논문과 위키피디아를 엄청 들여다봤는데 오랜만에 영어와 수식을 너무 많이 봐서 머리에 과부하가 걸린 상태다. 그리고 다른 건 몰라도 메르센 트위스트 같은 경우는… 아니 너무 복잡하다 인간적으로…

참고로 이거 만든 마츠모토 마코토 님은 지금 히로시마 대학교의 수학 대학원에서 조교수로 근무하고 계신데, 이 정도 머리가 되야 강단에 설 수 있는 건가라는 생각도 든다. 완전 어나더레벨…

이상으로 컴퓨터가 만드는 랜덤은 정말로 랜덤할까? 포스팅을 마친다.

참고문헌

XorShift – Wikipedia

There’s Math.random(), and then There’s Math.random() – V8 Dev

Futher scramblings of Marsaglia’s xorshift generators – Sebastiano Vigna

The Xorshit128+ random number generator fails BigCrush – Daniel Lemire

[Javascript] 난수 생성하기 (랜덤 숫자, Random Number)

Javascript로 난수를 생성하는 방법을 소개합니다.

Math.random() 범위를 지정한 난수 생성하기 0~9

0~10

0~99

0~100

1~10

2~5 난수 생성 함수 만들기 (범위 지정) min <= number <= max ( max 값 포함) min <= number < max (max 값 불포함) 1. Math.random() Math.random() Javascript에서 난수를 생성하기 위해서는, Math.random() 함수를 사용합니다. 이 함수는 0~1(1은 미포함) 구간에서 부동소수점의 난수를 생성합니다. const rand1 = Math.random(); const rand2 = Math.random(); const rand3 = Math.random(); document.write(rand1 + '
‘); document.write(rand2 + ‘
‘); document.write(rand3 + ‘
‘);

위 코드는 실행할 때마다 다른 난수를 생성합니다.

그리고, 그 값은 0~1 사이의 부동소수점 값입니다. (1은 포함하지 않습니다)

2. 범위를 지정한 난수 생성하기

Math.random() 함수는 0~1 사이의 부동소수점 난수를 생성합니다.

그렇다면, 정수인 난수를 생성하려면 어떻게 해야 할까요?

정수인 난수를 생성하기 위해서는

Math.random() 함수와 Math.floor() 함수를 함께 사용합니다.

Math.floor() 함수는 소수점 1번째 자리를 버림하여 정수를 리턴하는 함수입니다.

[Javascript] 반올림(round), 올림(ceil), 내림(floor) – 소수점, 음수,자리수 지정

예제

// (1) 0 <= random < 1 const rand1 = Math.random(); document.write('(1) ' + rand1 + '
‘); // (2) 0 const rand2 = Math.floor(Math.random()); document.write(‘(2) : ‘ + rand2 + ‘
‘); // (3) 0 <= random <= 9 const rand_0_9 = Math.floor(Math.random() * 10); document.write('(3) : ' + rand_0_9 + '
‘); // (4) 0 <= random <= 10 const rand_0_10 = Math.floor(Math.random() * 11); document.write('(4) : ' + rand_0_10 + '
‘); // (5) 0 <= random <= 99 const rand_0_99 = Math.floor(Math.random() * 100); document.write('(5) : ' + rand_0_99 + '
‘); // (6) 0 <= random <= 100 const rand_0_100 = Math.floor(Math.random() * 101); document.write('(6) : ' + rand_0_100 + '
‘); // (7) 1 <= random <= 10 const rand_1_10 = Math.floor(Math.random() * 10) + 1; document.write('(7) : ' + rand_1_10 + '
‘); // (8) 2 <= random <= 5 const rand_2_5 = Math.floor(Math.random() * 4) + 2; document.write('(8) : ' + rand_2_5 + '
‘);

(1) 0 <= random < 1 Math.random(); Math.random() 함수는 0~1 사이의 실수를 리턴합니다.(1 미포함) (2) 0 Math.floor(Math.random()); Math.floor() 함수는 소수점 1째자리 이후의 숫자를 버림하고, 정수를 리턴합니다. Math.random() 함수는 0~0.99999...인 숫자를 리턴하기 때문에, Math.floor(Math.random()) 의 결과는 항상 0입니다. (3) 0 <= random <= 9 Math.floor(Math.random() * 10); Math.random() 함수의 결과는 0~0.99999...이고, Math.random() * 10 의 결과는 0~9.99999...입니다. 따라서, Math.floor(Math.random() * 10)의 결과는 0~9 범위의 정수입니다. Math.random() 함수를 이용하여 정수 범위의 난수를 생성할 때는 이렇게 Math.random() 함수를 호출한 결과를 조작하고, Math.floor()를 사용합니다. (4) 0 <= random <= 10 Math.floor(Math.random() * 11); 10을 포함하는 정수 난수를 얻고 싶다면 Math.random() 함수의 결과에 11을 곱하고, 소수점 이하를 버림합니다. (5) 0 <= random <= 99 Math.floor(Math.random() * 100); 0~99 범위의 정수 난수를 생성하려면 Math.random() 함수의 결과에 100을 곱해주고, 소수점 이하를 버림합니다. (6) 0 <= random <= 100 Math.floor(Math.random() * 101); 0~100 범위의 정수 난수를 생성하려면 >Math.random() 함수의 결과에 101을 곱하고, 소수점 이하를 버림합니다.

(7) 1 <= random <= 10 Math.random() * 10) + 1; 최소값을 지정하고 싶을 때는 Math.random() * (max - min + 1) 값을 계산하고, 소수점 이하를 버림합니다. 그리고, min 값을 더해줍니다. 1~10 범위의 정수 난수를 계산하기 위해서 아래와 같이 계산하였습니다. Math.floor(Math.random() * (10 -1 + 1)) + 1 = Math.floor(Math.random() * 10) + 1 (8) 2 <= random <= 5 Math.floor(Math.random() * 4) + 2; (7)번 예제와 같이 범위를 지정하는 예제입니다. Math.floor(Math.random() * (5 - 2 + 1)) + 1 = Math.floor(Math.random() * 4) + 1 3. 난수 생성 함수 만들기 (범위 지정) min <= number <= max ( max 값 포함) function rand(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } document.writeln(rand(1, 3)); document.writeln(rand(77, 88)); min ~ max 값까지의 정수 랜덤 넘버를 만들어주는 함수입니다. min <= number < max (max 값 불포함) function rand(min, max) { return Math.floor(Math.random() * (max - min)) + min; } document.writeln(rand(1, 3)); document.writeln(rand(77, 88)); max값을 포함하지 않는, min ~ max 값까지의 정수 랜덤 넘버를 만들어주는 함수입니다. Math.random(), Math.floor() 함수를 이용해서 난수(random number)를 만드는 방법을 알아보았습니다.

3. 난수 생성()과 시간 관련 라이브러리() 소개>

이번 강좌에서는

라이브러리를 활용한 난수 생성

라이브러리를 활용한 시간 측정

에 대해서 알아봅니다.

안녕하세요 여러분! 이번 강좌에서는 C++ 11 에 추가된 난수(Random number)를 쉽게 생성할 수 있도록 도와주는 라이브러리와 시간 관련 데이터를 다룰 수 있게 도와주는 라이브러리를 살펴보도록 하겠습니다.

아무래도 C 언어를 먼저 접한 분들은 C++ 에서도 난수 생성이나 날짜 관련 계산을 위해 C 라이브러리 ( time.h 이나 stdlib.h ) 를 사용하는 경우가 종종 있는데 이번 기회에 왜 C++ 라이브러리를 사용해야만 하는지 짚고 넘어가도록 할 것입니다.

C 스타일의 난수 생성의 문제점

아래는 C 스타일로 0 부터 99 까지의 난수를 생성하는 코드 입니다.

#include #include #include int main () { srand ( time (NULL)); for ( int i = 0 ; i < 5 ; i ++ ) { printf ( "난수 : %d " , rand () % 100 ); } return 0 ; } 성공적으로 컴파일 하였다면 실행 결과 난수 : 75 난수 : 95 난수 : 20 난수 : 47 난수 : 41 와 같이 나옵니다. 참고로 위 코드는 엄밀히 말하자면 진짜 난수를 생성하는 것이 아니라 마치 난수 처럼 보이는 의사 난수 (pseudo random number) 을 생성하는 코드 입니다. 컴퓨터 상에서 완전한 무작위로 난수를 생성하는 것은 생각보다 어렵습니다. 그 대신에, 첫 번째 수 만 무작위로 정하고, 나머지 수들은 그 수를 기반으로 여러가지 수학적 방법을 통해서 마치 난수 처럼 보이지만 실제로는 무작위로 생성된 것이 아닌 수열들을 만들어내게 됩니다. 무작위로 정해진 첫 번째 수를 시드(seed) 라고 부르는데, C 의 경우 srand 를 통해 seed 를 설정할 수 있습니다. 우리의 경우 time(NULL) 을 통해 프로그램을 실행했던 초를 시드값으로 지정하였습니다. 그리고 rand() 는 호출 할 때 마다 시드값을 기반으로 무작위 처럼 보이는 수열을 생성하게 되죠. 하지만 위 코드는 여러가지 문제점들이 있습니다. 시드값이 너무 천천히 변한다. 보시다시피 시드값으로 현재의 초 를 지정하였습니다. 이 말은 즉슨 같은 시간대에 시작된 프로그램의 경우 모두 같은 의사 난수 수열을 생성한다는 점입니다. 만일 여러가지 프로그램들이 같이 돌아가는 시스템에서 위 코드를 사용하였다면 아마 같은 난수열을 생성하는 프로그램이 생기게 될 것입니다. 0 부터 99 까지 균등하게 난수를 생성하지 않는다. 위 코드에서 우리가 0 부터 99 까지 난수를 생성하기 위해서 printf ( "난수 : %d " , rand () % 100 ); 와 같이 하였습니다. 문제는 rand() 가 리턴하는 값이 0 부터 RAND_MAX 라는 점입니다. 물론 rand() 가 0 부터 RAND_MAX 까지의 모든 값들을 같은 확률로 난수를 생성하지만, 100 으로 나눈 나머지는 꼭 그러라는 법이 없습니다. 예를 들어서 RAND_MAX 가 128 이라고 합시다. 그렇다면 1 의 경우 rand() 가 리턴한 값이 1 이거나 101 일 때 생성되지만 50 의 경우 rand() 가 리턴한 값이 50 일 때만 생성됩니다. 따라서 1 이 뽑힐 확률이 50 이 뽑힐 확률 보다 2 배나 높게 됩니다. rand() 자체도 별로 뛰어나지 않다. 무엇보다도 C 의 rand() 함수는 선형 합동 생성기 (Linear congruential generator) 이라는 알고리즘을 바탕으로 구현되어 있는데 이 알고리즘은 썩 좋은 품질의 난수열을 생성하지 못합니다. 더 깊게 설명하자면 생성되는 난수열들의 상관 관계가 높아서 일부 시뮬레이션에 사용하기에 적합하지 않습니다. 결론적으로 말하자면 주의 사항 C++ 에서는 C 의 srand 와 rand 는 갖다 버리자!

먼저 위 코드 처럼 0 부터 99 까지의 난수를 생성하는 코드를 C++ 의 라이브러리를 사용해서 어떻게 작성하는지 살펴보도록 하겠습니다.

#include #include int main () { // 시드값을 얻기 위한 random_device 생성. std :: random_device rd; // random_device 를 통해 난수 생성 엔진을 초기화 한다. std :: mt19937 gen ( rd ()); // 0 부터 99 까지 균등하게 나타나는 난수열을 생성하기 위해 균등 분포 정의. std :: uniform_int_distribution < int > dis ( 0 , 99 ); for ( int i = 0 ; i < 5 ; i ++ ) { std :: cout << "난수 : " << dis (gen) << std :: endl; } } 성공적으로 컴파일 하였다면 실행 결과 난수 : 77 난수 : 11 난수 : 45 난수 : 72 난수 : 3 자 그렇다면 위 코드를 하나 하나씩 살펴보도록 하겠습니다. // 시드값을 얻기 위한 random_device 생성. std :: random_device rd; 앞서 C 의 경우 time(NULL) 을 통해서 시드값을 지정하였지만 이는 여러가지 문제점이 있었습니다. C++ 에서는 좀더 양질의 시드값을 얻기 위해 random_device 라는 것을 제공합니다. 대부분의 운영체제에는 진짜 난수값들을 얻어낼 수 있는 여러가지 방식들을 제공하고 있습니다. 예를 들어서 리눅스의 경우 /dev/random 나 /dev/urandom 을 통해서 난수값을 얻을 수 있습니다. 이 난수값은, 이전에 우리가 이야기 하였던 무슨 수학적 알고리즘을 통해 생성되는 가짜 난수가 아니라 정말로 컴퓨터가 실행하면서 마주치는 무작위적인 요소들 (예를 들어 장치 드라이버들의 noise) 을 기반으로한 진정한 난수를 제공합니다. random_device 를 이용하면 운영체제 단에서 제공하는 진짜 난수를 사용할 수 있습니다. 다만 진짜 난수의 경우 컴퓨터가 주변의 환경과 무작위적으로 상호작용하면서 만들어지는 것이기 때문에 의사 난수보다 난수를 생성하는 속도가 매우 느립니다. 따라서 시드값처럼 난수 엔진을 초기화 하는데 사용하고, 그 이후의 난수열은 난수 엔진으로 생성하는 것이 적합합니다. // random_device 를 통해 난수 생성 엔진을 초기화 한다. std :: mt19937 gen ( rd ()); 위와 같이 생성한 random_device 객체를 이용해서 난수 생성 엔진 객체를 정의할 수 있습니다. 만약에 random_device 대신에 그냥 여러분이 원하는 값을 시드값으로 넣어주고 싶다면 그냥 std :: mt19937 gen ( 1234 ); 와 같이 해도 됩니다. std::mt19937 는 C++ 라이브러리에서 제공하는 난수 생성 엔진 중 하나로, 메르센 트위스터 라는 알고리즘을 사용합니다. 이 알고리즘은 기존에 rand 가 사용하였던 선형 합동 방식 보다 좀 더 양질의 난수열을 생성한다고 알려져있습니다. 무엇보다도 생성되는 난수들 간의 상관관계가 매우 작기 때문에 여러 시뮬레이션에서 사용할 수 있습니다.

참고적으로 라이브러리에는 위 메르센 트위스터 기반 엔진 말고도 기존의 rand 와 같이 선형 합동 알고리즘을 사용한 minstd_rand 외 여러가지 엔진들이 정의되어 있습니다. 물론 mt19937 이 훌륭한 난수를 생성하기에는 적합하지만 생각보다 객체 크기가 커서 (2KB 이상) 메모리가 부족한 시스템에서는 오히려 minstd_rand 가 적합할 수 있습니다. mt19937 를 생성한 이후에 난수를 생성하는 작업은 매우 빠릅니다.

이처럼 난수 생성 엔진을 만들었지만 아직 바로 난수를 생성할 수 있는 것은 아닙니다. C++ 의 경우 어디에서 수들을 뽑아낼지 알려주는 분포(distribution) 을 정의해야 합니다.

앞서 우리의 경우 0 부터 99 까지 균등한 확률로 정수를 뽑아내고 싶다고 하였습니다. 따라서 이를 위해선 아래와 같이 균등 분포 (Uniform distribution) 객체를 정의해야 합니다.

// 0 부터 99 까지 균등하게 나타나는 난수열을 생성하기 위해 균등 분포 정의. std :: uniform_int_distribution < int > dis ( 0 , 99 );

위와 같이 uniform_int_distribution 의 생성자에 원하는 범위를 써넣으면 됩니다.

for ( int i = 0 ; i < 5 ; i ++ ) { std :: cout << "난수 : " << dis (gen) << std :: endl; } 그리고 마지막으로 균등 분포에 사용할 난수 엔진을 전달함으로써 균등 분포에서 무작위로 샘플을 뽑아낼 수 있습니다. 라이브러리에서는 균등 분포 말고도 여러가지 분포들을 제공하고 있습니다. 여기서는 다 일일히 소개하기 어렵지만 그 중 가장 많이 쓰이는 정규 분포 (Normal distribution) 만 간단히 살펴보겠습니다. (전체 목록은 여기서 보세요.)

#include #include #include

#include int main () { std :: random_device rd; std :: mt19937 gen ( rd ()); std :: normal_distribution < double > dist ( /* 평균 = */ 0 , /* 표준 편차 = */ 1 ); std :: map < int , int > hist{}; for ( int n = 0 ; n < 10000 ; ++ n) { ++ hist[std :: round ( dist (gen))]; } for ( auto p : hist) { std :: cout << std :: setw ( 2 ) << p . first << ' ' << std :: string (p . second / 100 , '*' ) << " " << p . second << ' ' ; } } 성공적으로 컴파일 하였다면 실행 결과 -4 1 -3 38 -2 ****** 638 -1 ************************ 2407 0 ************************************** 3821 1 ************************ 2429 2 ***** 595 3 70 4 1 코드를 보면 간단합니다. std :: normal_distribution < double > dist ( /* 평균 = */ 0 , /* 표준 편차 = */ 1 );

이번에는 평균 0 이고 표준 편차가 1 인 정규 분포를 정의하였고,

for ( int n = 0 ; n < 10000 ; ++ n) { ++ hist[std :: round ( dist (gen))]; } 이를 바탕으로 위와 같이 정규 분포에서 10000 개의 샘플을 무작위로 뽑아내게 됩니다. 실제로 위 그림 처럼 아름다은 정규 분포 곡선이 나옴을 확인할 수 있습니다. 자 그럼 이것으로 random 라이브러리 사용법을 간단히 알아보았습니다. 이번에는 C++ 11 에 같이 추가된 시간 관련 데이터를 쉽게 계산할 수 있도록 도와주는 chrono 라이브러리를 살펴보도록 하겠습니다. chrono 소개 는 크게 아래와 같이 3 가지 요소들로 구성되어 있습니다.

현재 시간을 알려주는 시계 – 예를 들어서 system_clock

특정 시간을 나타내는 time_stamp

시간의 간격을 나타내는 duration

로 말이지요.

chrono 에서 지원하는 clock 들

에서는 여러가지 종류의 시계들을 지원하고 있습니다. 예를 들어서 일반적 상황에서 현재 컴퓨터 상 시간을 얻어 오기 위해서는 std::system_clock 을 사용하면 되고, 좀더 정밀한 시간 계산이 필요한 경우 (예를 들어 프로그램 성능을 측정하고 싶을 때) std::high_resolution_clock 을 사용하시면 됩니다.

이들 객체의 이름이 시계이기는 하지만 실제 시계 처럼 지금 딱 몇 시 이렇게 이야기 해주는 것이 아닙니다. 그 대신에, 지정된 시점으로 부터 몇 번의 틱(tick)이 발생 하였는지 알려주는 time_stamp 객체 를 리턴합니다. 예를 들어서 std::system_clock 의 경우 1970 년 1월 1일 부터 현재 까지 발생한 틱의 횟수를 리턴한다고 보시면 됩니다. 이를 흔히 유닉스 타임(Unix time) 이라 부릅니다. 쉽게 말해 time_stamp 객체는 clock 의 시작점과 현재 시간의 duration 을 보관하는 객체 입니다.

여기서 틱이라고 하면 시계의 초침이 한 번 똑딱 거리는 것이라 생각하면 됩니다. 컴퓨터의 경우도 내부에 시계가 있어서 특정 진동수로 똑딱 거리게 됩니다. 우리나라는 시계가 똑딱 거린다고 하지만 미국에서는 tick-tock 이라 하죠

각 시계 마다 정밀도가 다르기 때문에 각 clock 에서 얻어지는 tick 값 자체는 조금씩 다릅니다. 예를 들어서 system_clock 이 1 초에 1 tick 이라면, high_resolution_clock 의 경우 0.00000001 초 마다 1 tick 움직일 수 있습니다.

자 그렇다면 실제로 chrono 라이브러리를 사용하는 코드를 살펴보도록 하겠습니다. 아래 코드는 난수를 생성 속도를 측정합니다.

#include #include #include #include #include int main () { std :: random_device rd; std :: mt19937 gen ( rd ()); std :: uniform_int_distribution <> dist ( 0 , 1000 ); for ( int total = 1 ; total <= 1000000 ; total *= 10 ) { std :: vector < int > random_numbers; random_numbers . reserve (total); std :: chrono :: time_point < std :: chrono :: high_resolution_clock > start = std :: chrono :: high_resolution_clock :: now (); for ( int i = 0 ; i < total; i ++ ) { random_numbers . push_back ( dist (gen)); } std :: chrono :: time_point < std :: chrono :: high_resolution_clock > end = std :: chrono :: high_resolution_clock :: now (); // C++ 17 이전 auto diff = end – start; // C++ 17 이후 // std::chrono::duration diff = end – start; std :: cout << std :: setw ( 7 ) << total << "개 난수 생성 시 틱 횟수 : " << diff . count () << std :: endl; } } 성공적으로 컴파일 하였다면 실행 결과 1개 난수 생성 시 틱 횟수 : 535 10개 난수 생성 시 틱 횟수 : 1370 100개 난수 생성 시 틱 횟수 : 11354 1000개 난수 생성 시 틱 횟수 : 110219 10000개 난수 생성 시 틱 횟수 : 1145811 100000개 난수 생성 시 틱 횟수 : 11040923 1000000개 난수 생성 시 틱 횟수 : 99170277 와 같이 나옵니다. std :: chrono :: time_point < std :: chrono :: high_resolution_clock > start = std :: chrono :: high_resolution_clock :: now ();

먼저 high_resolution_clock 으로 부터 현재의 time_point 를 얻어오는 코드부터 살펴봅시다. chrono 라이브러리의 경우 다른 표준 라이브러리와는 다르게 객체들이 std::chrono 이름 공간 안에 정의되어 있습니다. 따라서 high_resolution_clock 를 쓰기 위해서는 std::high_resolution_clock 가 아니라 std::chrono::high_resolution_clock 와 같이 적어야 합니다.

만약에 매번 std::chrono 를 쓰기에 번거롭다면 그냥

namespace ch = std :: chrono;

와 같이 ch 라는 별명을 지어주고 ch 로 대체하면 됩니다.

이들 clock 에는 현재의 time_point 를 리턴하는 static 함수인 now 가 정의되어 있습니다. 이 now() 를 호출하면 위와 같이 해당 clock 에 맞는 time_point 객체를 리턴합니다. 우리의 경우 high_resolution_clock::now() 를 호출하였으므로, std::chrono::time_point 를 리턴합니다.

time_point 가 clock 을 왜 템플릿 인자로 가지는지는 앞서도 설명하였듯이 clock 마다 1 초에 발생하는 틱 횟수가 모두 다르기 때문에 나중에 실제 시간으로 변환 시에 어떤 clock 을 사용했는지에 대한 정보가 필요하기 때문입니다.

auto diff = end – start;

자 이제 난수 생성이 끝나면 end 에 끝나는 시간을 또 받아서 그 차이를 계산해야 합니다. 위와 같이 두 time_stamp 를 빼게 된다면 duration 객체를 리턴합니다.

주의 사항

참고로 C++ 17 이전에서는 end – start 가 리턴하는 duration 객체의 템플릿 인자를 전달해야 합니다. 따라서 굳이 duration 의 템플릿 인자들을 지정하기 보다는 속시원하게 그냥 auto diff = end – start 로 하는게 낫습니다.

std :: cout << std :: setw ( 7 ) << total << "개 난수 생성 시 틱 횟수 : " << diff . count () << std :: endl; duration 에는 count 라는 멤버 함수가 정의되어 있는데 이는 해당 시간 차이 동안 몇 번의 틱이 발생하였는지를 알려줍니다. 하지만 우리에게 좀 더 의미 있는 정보는 틱이 아니라 실제 시간으로 얼마나 걸렸는지 알아내는 것이지요. 이를 위해선 duration_cast 를 사용해야 합니다. #include #include #include #include #include namespace ch = std :: chrono; int main () { std :: random_device rd; std :: mt19937 gen ( rd ()); std :: uniform_int_distribution <> dist ( 0 , 1000 ); for ( int total = 1 ; total <= 1000000 ; total *= 10 ) { std :: vector < int > random_numbers; random_numbers . reserve (total); ch :: time_point < ch :: high_resolution_clock > start = ch :: high_resolution_clock :: now (); for ( int i = 0 ; i < total; i ++ ) { random_numbers . push_back ( dist (gen)); } ch :: time_point < ch :: high_resolution_clock > end = ch :: high_resolution_clock :: now (); auto diff = end – start; std :: cout << std :: setw ( 7 ) << total << "개 난수 생성 시 걸리는 시간: " << ch :: duration_cast < ch :: microseconds > (diff) . count () << "us" << std :: endl; } } 성공적으로 컴파일 하였다면 실행 결과 1개 난수 생성 시 걸리는 시간: 0us 10개 난수 생성 시 걸리는 시간: 1us 100개 난수 생성 시 걸리는 시간: 10us 1000개 난수 생성 시 걸리는 시간: 101us 10000개 난수 생성 시 걸리는 시간: 1033us 100000개 난수 생성 시 걸리는 시간: 10702us 1000000개 난수 생성 시 걸리는 시간: 98950us 와 같이 나옵니다. ch :: duration_cast < ch :: microseconds > (diff) . count ()

duration_cast 는 임의의 duration 객체를 받아서 우리가 원하는 duration 으로 캐스팅 할 수 있습니다. std::chrono::microseconds 는 에 미리 정의되어 있는 duration 객체 중 하나로, 1 초에 $10^6$ 번 틱을 하게 됩니다. 따라서 microseconds 로 캐스팅 한뒤에 리턴하는 count 값은 해당 duration 이 몇 마이크로초 인지를 나타내는 것이지요.

우리의 경우 1000000 개의 난수를 생성하는데 불과 98950 마이크로초, 대량 98 밀리초 정도 걸린다고 나왔습니다. 에는 std::chrono::microseconds 외에도 nanoseconds, milliseconds, seconds, minutes, hours 가 정의되어 있기 때문에 상황에 맞게 사용하시면 됩니다.

현재 시간을 날짜로

안타깝게도 C++ 17 까지에서는 chrono 라이브러리 상에서 날짜를 간단하게 다룰 수 있도록 도와주는 클래스가 없습니다. 예를 들어서 현재 시간을 출력하고 싶다면 C 의 함수들에 의존해야 합니다.

예를 들어서 현재 시간을 출력하는 코드를 살펴봅시다.

#include #include #include #include int main () { auto now = std :: chrono :: system_clock :: now (); std :: time_t t = std :: chrono :: system_clock :: to_time_t (now); std :: cout << "현재 시간은 : " << std :: put_time (std :: localtime ( & t), "%F %T %z" ) << ' ' ; } 성공적으로 컴파일 하였다면 실행 결과 현재 시간은 : 2020-01-06 00:28:08 +0900 먼저 system_clock 에서 현재의 time_point 를 얻어온 후에, 날짜를 출력하기 위해서 time_t 객체로 변환해야 합니다. std :: time_t t = std :: chrono :: system_clock :: to_time_t (now); 이를 위해 위와 같이 각 clock 이 제공하는 static 함수인 to_time_t 를 사용하면 됩니다. std :: cout << "현재 시간은 : " << std :: put_time (std :: localtime ( & t), "%F %T %z" ) << ' ' ; 그 다음에 현재 시간을 std::tm 객체로 변환하기 위해서 std::localtime 에 t 를 전달하였고, 마지막으로 std::put_time 을 사용해서 우리가 원하는 형태의 문자열로 구성할 수 있게 됩니다. 참고로 put_time 에 전달된 인자인 "%F %T %z" 는 strftime 에서 사용되는 인자와 동일합니다. 따라서 %F 와 같은 것들이 무엇을 수행하는지 알고 싶다면 해당 함수 레퍼런스를 참고하시기 바랍니다. 안타깝게도 C++ 17 현재 C 의 함수들을 이용하지 않고서 날짜를 다룰 수 있는 방법은 없습니다. 하지만 C++ 20 부터 에 C 라이브러리 필요 없이 날짜를 다룰 수 있는 클래스와 함수들이 추가된다고 하니 조금만 기다려주시기 바랍니다!

그렇다면 이번 강좌는 여기에서 마치도록 하겠습니다. 다음 강좌에서는 C++ 17 에서 등장한 라이브러리를 살펴볼 것입니다.

뭘 배웠지? 난수를 생성하기 위해서 C 의 srand 와 rand 를 사용하지 말자. 대부분의 상황에서는 std::mt19937 로 충분히 양질의 난수를 뽑아낼 수 있다. 특히 라이브러리를 사용할 경우 원하는 확률 분포에서 샘플링할 수 있다. 현재 시간을 알기 위해서는 system_clock 을 사용하면 되고, 좀더 정밀한 측정이 필요할 경우 high_resolution_clock 을 사용하면 된다. duration_cast 를 이용해서 원하는 시간 스케일로 변환할 수 있다.

난수 발생

CPRNG(의사 난수 발생기)는 보안 소프트웨어의 중요한 구성 요소입니다. 이를 위해 Apple은 iOS, iPadOS, macOS, tvOS 및 watchOS 커널에서 실행되는 신뢰하는 소프트웨어 CPRNG를 지원합니다. 이는 시스템에서 raw 엔트로피를 집계하고 커널과 사용자 공간 모두에서 안전한 무작위 번호를 소비자에게 제공하는 것을 담당합니다.

Intel 임의 명령(예 : RDSEED 및 RDRAND(Intel 기반 Mac에만 해당)

시동 과정에 걸쳐 엔트로피를 유지하는 데 사용되는 시드 파일

커널 CPRNG는 기기를 시동하는 동안과 기기의 수명 주기에 걸쳐 여러 엔트로피 소스에서 시드됩니다. 이는 다음을 포함합니다(가능 여부에 따라).

커널 CPRNG

커널 CPRNG는 256비트 보안 수준을 대상으로 하는 Fortuna 파생 설계입니다. 다음과 같은 API를 통해 사용자 공간 소비자에게 고품질의 무작위 번호를 제공합니다.

getentropy (2) 시스템 호출

임의의 기기(/dev/random)

커널 CPRNG는 임의의 기기에 쓰기를 통해 사용자 제공 엔트로피를 수용합니다.

키워드에 대한 정보 난수 발생

다음은 Bing에서 난수 발생 주제에 대한 검색 결과입니다. 필요한 경우 더 읽을 수 있습니다.

이 기사는 인터넷의 다양한 출처에서 편집되었습니다. 이 기사가 유용했기를 바랍니다. 이 기사가 유용하다고 생각되면 공유하십시오. 매우 감사합니다!

사람들이 주제에 대해 자주 검색하는 키워드 컴퓨터가 만드는 랜덤숫자의 진실(feat.의사 난수) – [高지식] 거니

  • 랜덤넘버
  • 랜덤
  • 랜덤번호
  • 난수
  • 의사난수
  • 수도 랜덤
  • rand()
  • 컴퓨터 랜덤
  • 컴퓨터
  • 컴퓨터 난수
  • 원리
  • 강화
  • 확률
  • 확률 게임
  • 컴퓨터 확률
  • 확률 랜덤
  • 랜덤 확률
  • 고지식
  • 거니
  • 알고투게더
  • 알고리즘
  • 자료구조
  • C언어
  • 자바
  • 코딩
  • Gunny

컴퓨터가 #만드는 #랜덤숫자의 #진실(feat.의사 #난수) # #- # #[高지식] #거니


YouTube에서 난수 발생 주제의 다른 동영상 보기

주제에 대한 기사를 시청해 주셔서 감사합니다 컴퓨터가 만드는 랜덤숫자의 진실(feat.의사 난수) – [高지식] 거니 | 난수 발생, 이 기사가 유용하다고 생각되면 공유하십시오, 매우 감사합니다.

Leave a Comment