Random, TIme

하루공부·2024년 1월 18일
0

C++

목록 보기
6/25
post-thumbnail

C++ 아이콘 제작자: Darius Dan - Flaticon


random

  • c언어의 난수는 내가 보통 생각하던 완전한 무작위 난수가 아닌 의사 난수이다.
    ==> 기초값으로 이미 정해진 과정에 따라 그냥 만들어진다.

    • 보통 시드값을 time(NULL)로 조절하는데 이 단위는 초단위이기 때문에 변화 폭이 좁다
      ==> 시간대가 비슷하거나 같이 돌아가는 프로그램은 같은 수를 만든다.
    • 난수가 정확히 균등하지 않으며 rand()는 선형 합동 생성기 (Linear congruential generator) 이라는 알고리즘을 바탕으로 구현 ==> 좋다고 말하지 못하는 품질의 난수를 만든다.
  • random 헤더 파일을 추가하며 사용한다.

  • 4개의 난수 생성기가 있다

    • random_device : 비결정적 난수를 생성
      ==> 컴퓨터의 하드웨어를 통하여 사용을 하며 하드웨어로 구현을 할 수 없으면 정의된 의사 난수 엔진 중에서 라이브러리가 선택한다.
      ==> 특징은 엔트로피(entropy, 무질서도)로 측정
    • inear_congruential_engine : 선형 합동 난수 엔진
      ==> 선형 합동 생성기를 기반인 난수 엔진, 생성된 결과는 정수만이 생성된다.
      ==> 현재 상태를 저장하기 위한 메모리 사용량이 가장 적다 -> 생성된 난수(정수) 또는 아직 생성안되어 초기 시드값을 담은 정수 2가지.
      ==> 간단하지만 가장 빠르거나 높은 풀질을 가지는 것은 아니다.
    • mersenne_twister_engine : 메르센 트위스터 난수 엔진
      ==> 소프트웨어 기반 난수 생성기 중에서 가장 품질이 좋고 속도가 빠른 엔진이다.
      ==> 하지만 암호학적으로 안전하지 않은 부호 없는 정수 난수를 간격에 생성한다.
    • ubtract_with_carry_engine : 감산 캐리 난수 엔진
      ==> 매우 많은 상태의 메모리에 저장하지만 속도와 품지이 mersenne_twister_engine 보다 낮다.

난수 엔진

  • random number engine adapter는 난수 생성에 사용되는 엔진을 조정하여 원하는 값을 도출시킴.
    ==> 난수를 생성하기 위해 시드를 통하여 엔진 객체를 만들어준다.
#include <iostream>
#include <random>
int main(){
   std::random_device rd; // 시드값 생성
   std::mt19937 eng(rd());  // 엔진에 난수의 시드값을 넣어 엔진 객체를 만들어준다.
   std::uniform_int_distribution<int> dis(0, 99); 
   for (int i = 0; i < 5; i++) { std::cout << "난수 : " << dis(eng) << std::endl; }
}

많은 엔진이 있지만 여기서 1개만 알알보자
mt19937엔진은 생성되는 난수들 간의 상관관계가 매우 작아 시뮬레이션에 사용.


분포

  • 위 예시 처럼 엔진을 만들었다고 난수를 생성할 수 있는게 아님 c++은 어디에서 난수를 가져올지 분포를 정의해야 한다. ==> 분포는 일정 범위에 숫자가 분포된 방식을 표현하는 수학 공식이다.

    std::uniform_int_distribution<int> dis(0, 99);

    위 예시에서 생성된 엔진을 균등 분포 객체를 정의, 생성자로 범위를 지정
    그 후 분포에 엔진을 전달 함을 범위에 있는 수를 뽑아낸다.

  • 많은 분포들이 있고 그중에 가장 많이 쓰는 정규 분포를 알아보자
    std::normal_distribution<double> dist(0, 1);

    <>에 생성될 난수의 타입과 매개변수로 평균과 표준 편차를 정해준다.



chrono

  • chrono는 크게 3가지로 구성됨
    • 현재 시간을 알려주는 시계 - system_clock, high_resolution_clock 등 ...
      • std::system_clock은 현재 컴퓨터 상 시간을 얻어온다
        ==> 1970 년 1월 1일 부터 현재 까지 발생한 틱의 횟수를 리턴
      • std::high_resolution_clock은 정밀한 시간 계산이 필요한 경우
    • 시간의 간격을 나타내는 - duration
    • 특정 시간을 나타내는 - time_stamp ==> clock의 시작점과 특정 시간의 duration을 보관하는 객체
  • 실제 시계 처럼 지금 딱 몇 시 이렇게 이야기 해주는 것이 아니다.
    지정된 시점으로 부터 몇 번의 틱(tick)이 발생 하였는지 알려주는 time_stamp 객체를 리턴

    각 시계 마다 정밀도가 다르기 때문에 각 clock 에서 얻어지는 tick 값 자체는 조금씩 다르다.
    가령 system_clock이 1초에 1tick이라면 high_resolution_clock은 0.00000001 초 마다 1 tick

#include <chrono>
#include <iomanip>
#include <iostream>
#include <random>
#include <vector>

int main() {
    std::random_device rd;
    std::mt19937 eng(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(eng)); }
        std::chrono::time_point<std::chrono::high_resolution_clock> end = std::chrono::high_resolution_clock::now();
        auto diff = end - start; // C++ 17 이전 
        // C++ 17 이후 ==> std::chrono::duration diff = end - start; 
        std::cout << std::setw(7) << total << "개 난수 생성 시 틱 횟수 : " << diff.count() << std::endl;
    }
}

위 예시의

std::chrono::time_point<std::chrono::high_resolution_clock> start = std::chrono::high_resolution_clock::now();
std::chrono::time_point<std::chrono::high_resolution_clock> end =  std::chrono::high_resolution_clock::now();

에서 시계의 멤버 함수인 now()로 해당 시계(clock)에 맞는 time point를 리턴한다.

time_point

  • 특정한 시점을 표현하는 클래스로 에포크(epoch, 기준시간)을 기준으로 측정한 duration을 저장.
  • 기준시간(에포크)는 특정 clock의 시작 시간이 된다.
  • 그래서 time point 객체를 생성하기 위해 특정 clock을 <>에 전달해야 한다.


  • 다시 위 예제로 돌아가면 now() 를 호출하면 위와 같이 해당 clock 에 맞는 time_point 객체를 리턴한다.

auto diff = end - start;

  • 위와 같이 2개의 time_point를 빼면 duration 객체를 리턴한다


diff.count()

  • duration 객체의 count라는 멤버하수는 해당 시간 차이동안 몇 번의 틱이 발생했는지 알려준다.

  • 하지만 틱이 아닌 사람이 알아볼 수 있는 시간을 표현해야 한다.
    ==> duration_cast<>()
    ==> 해당 함수는 chrono의 멤버 함수로 duration객체를 받아서 원하는 duration으로 캐스팅한다

    위 예제에서는 ch::microseconds 마이크로초 단위로 표현했다
    이외에 nanoseconds, milliseconds, seconds, minutes, hours도 있다


  • 그런데 안타깝게 C++에는 현재시간을 간단하게 다룰 수 있는 클래스가 없어 c함수에 의존해야 한다.

  • 먼저 system_clock 에서 현재의 time_point 를 얻어온 후에, 날짜를 출력하기 위해서 time_t 객체로 변환.

    • time이라 함은 크게 time_t 타입과 tm구조체가 있다.
      • time_t 타입은 시간을 나타내는데 1970년 1월 1일 00:00 UTC (유닉스 타임)이후 경과된 초(정수값)를 나타낸다. (time_t 타입은 ctime 헤더 파일에 정의)
      • tm 구조체 이는 우리가 알아보기 쉽게 변수로 나눠진 구조체다.
  • system_clock에는 time_point와 time_t 타입의 C스타일 시간을 상호 변환하는 static 함수가 2개 있다

  1. to_time_t()는 인수로 전달 받는 time_point를 time_t로 리턴
  2. form_time_t()는 인수로 전달 받는 time_t를 time_point로 리턴
  • 그래서 time_point를 time_t로 변환하기 위해 std::chrono::system_clock::to_time_t(now);를 사용 ==> 그렇게 반환한 값을 tm구조체에 있는 원하는 형태로 바꾼다.
    ==> 그런데 time_t 타입은 마냥 정수로만 이루어진 숫자이다 -> 알아볼 수 없다.
    ==> tm 구조체로 바꿔서 우리가 알아보기 쉽게 만들자.

    struct tm localtime(const time_t pTime);
    ==> const time_t* 타입의 변수를 매개변수로 받고, 그 시간을 변환해서 tm에 딱딱 이쁘게 넣어서 반환해주는 그런 함수

    • time_t에는 tm 구조체로 변환하는 gmtime(gmtime_s)과 localtime(localtime_s) 함수가 있다
    • 반대로 struct tm 형식을 time_t 형식으로 변환하는 mktime(mkgmtime, timegm)이 있다
    • C++ 2019 기준으로 gmtime 과 localtime 은 deprecate(컴파일 경고) 되었다
      • localtime_s(tm 객체 주소, time_t 객체 주소); ==> 시스템의 타임존에 맞는 시간
      • gmtime_s(tm 객체 주소, time_t 객체 주소); ==> UTC(세계협정시간)
  • 마지막으로 문자열로 바꿔주는 함수

    • char ctime(const time_t pTime); time_t 객체를 넣으면 그대로 요일 월 날짜 시간 분 초 년도 를 출력함
    • char asctime(const struct tm pTm); 위와 같지만 tm객체를 넣어야함
    • 반환형 : "요일 월 일 시간:분:초 연도" ("Www Mmm dd hh:mm:ss yyyy")

random_device
inear_congruential_engine
inear_congruential_engine
std::mersenne_twister_engine
std::tm
공부한 내용 복습

개인 공부 기록용 블로그입니다.
틀린 부분 있으다면 지적해주시면 감사하겠습니다!!

0개의 댓글