테스트 응용 프로그램의 가장 근본적인 문제는 srand
한 번만 호출 한 다음 한 번만 호출 rand
하고 종료 한다는 것 입니다.
전체 srand
기능 포인트 는 임의의 시드 로 의사 난수의 시퀀스 를 초기화 하는 것 입니다.
즉 , 동일한 / 구현을 사용하여 두 개의 다른 응용 프로그램에서 동일한 값 을 전달 하면srand
두 응용 프로그램에서 동일한 값 시퀀스 를 읽습니다.srand
rand
rand()
그러나 귀하의 응용 프로그램에서 의사 랜덤 시퀀스는 하나의 요소로 구성됩니다-현재의 시간과 같은 시드에서 생성 된 의사 랜덤 시퀀스의 첫 번째 요소 second
정밀도 . 그러면 출력에서 무엇을 볼 것으로 예상됩니까?
분명히 동일한 초에 응용 프로그램을 실행할 때 동일한 시드 값을 사용하므로 결과는 물론 동일합니다 (마틴 요크가 이미 질문에 대한 의견에서 언급했듯이).
실제로 srand(seed)
한 번 호출 한 다음 rand()
여러 번 호출 하고 해당 시퀀스를 분석해야합니다. 무작위로 표시되어야합니다.
편집하다:
아, 알겠습니다. 분명히 구두 설명으로는 충분하지 않습니다 (언어 장벽 또는 무언가 ... :)).
확인. srand()/rand()/time()
질문에 사용 된 것과 동일한 함수를 기반으로하는 구식 C 코드 예제 :
#include <stdlib.h>
#include <time.h>
#include <stdio.h>
int main(void)
{
unsigned long j;
srand( (unsigned)time(NULL) );
for( j = 0; j < 100500; ++j )
{
int n;
/* skip rand() readings that would make n%6 non-uniformly distributed
(assuming rand() itself is uniformly distributed from 0 to RAND_MAX) */
while( ( n = rand() ) > RAND_MAX - (RAND_MAX-5)%6 )
{ /* bad value retrieved so get next one */ }
printf( "%d,\t%d\n", n, n % 6 + 1 );
}
return 0;
}
^^^ THAT 프로그램의 단일 실행의 순서는 무작위로보고되어있다.
EDIT2 :
C 또는 C ++ 표준 라이브러리를 사용할 때는 현재 표준 데이터에 의해 결정된 임의의 데이터를 실제로 생성하는 단일 표준 함수 나 클래스가 없다는 것을 이해하는 것이 중요합니다. 이 문제에 접근하는 유일한 표준 도구는 std :: random_device 인데 불행히도 여전히 실제 임의성을 보장하지는 않습니다.
응용 프로그램의 특성에 따라 실제로 임의의 (예측할 수없는) 무작위 데이터가 필요한지 결정해야합니다. 가장 확실하게 진정한 무작위성이 필요한 경우 주목할만한 사례 이 는 정보 보안입니다 (예 : 대칭 키, 비대칭 개인 키, 솔트 값, 보안 토큰 등 생성).
그러나 보안 등급 난수는 별도의 기사를 쓸 가치가있는 별도의 산업입니다.
대부분의 경우 의사 난수 생성기 는 충분합니다 (예 : 과학 시뮬레이션 또는 게임). 어떤 경우에는 일관되게 정의 된 의사 랜덤 시퀀스가 필요합니다. 예를 들어 게임에서는 많은 데이터를 저장하지 않기 위해 런타임에 정확히 동일한 맵을 생성하도록 선택할 수 있습니다.
원래의 질문과 다수의 동일 / 유사한 질문 (및 그에 대한 많은 오도 된 "답변")은 가장 먼저 난수를 의사 난수와 구별하고 의사 난수 숫자 시퀀스가 무엇인지 이해하는 것이 중요하다는 것을 나타냅니다 의사 난수 생성기는 실제 난수 생성기를 사용할 수있는 것과 같은 방식으로 사용되지 않습니다.
직관적으로 난수를 요청할 때 반환 된 결과는 이전에 반환 된 값에 의존해서는 안되며 누군가가 이전에 요청한 적이 있고 어떤 순간과 프로세스에 따라 어떤 컴퓨터와 발전기에서 어떤 컴퓨터에 의존하는지에 의존해서는 안됩니다 요청 된 은하계 그것은 "무작위" 라는 단어가 결국 예측할 수없고 무엇이든 독립적이라는 의미입니다. 그렇지 않으면 더 이상 무작위가 아닙니다. 이 직관을 사용하면 가능한 어떤 맥락에서든 임의의 숫자를 얻기 위해 캐스팅 할 마법 주문을 웹에서 검색하는 것이 당연합니다.
^^^ 그런 종류의 직관적 인 기대는 매우 잘못되어 의사 난수 생성기를 포함하는 모든 경우에 해 롭습니다 . 합니다. 실제 난수에 대해서도 합리적입니다.
"임의의 숫자"라는 의미가 있지만 "의사 난수"와 같은 것은 없습니다. 의사 난수 생성기는 실제로 의사 난수 생성 순서를 .
전문가가 PRNG의 품질에 대해 이야기 할 때 실제로 생성 된 시퀀스의 통계적 특성 (및 주목할만한 하위 순서)에 대해 이야기합니다. 예를 들어, 두 개의 고품질 PRNG를 차례로 사용하여 결합하는 경우 각각 좋은 시퀀스를 개별적으로 생성하더라도 나쁜 결과 시퀀스를 생성 할 수 있습니다 (두 개의 우수한 시퀀스는 서로 단순히 상관되어 잘못 결합 될 수 있음).
의사-랜덤 시퀀스는 실제로 항상 결정적입니다 (알고리즘과 초기 매개 변수로 미리 결정됨 ).
특히 rand()
/ srand(s)
함수 쌍은 구현 정의 알고리즘으로 생성 된 단일 프로세스 비스 레드 안전 (!) 의사 난수 시퀀스를 제공합니다. 함수 rand()
는 범위 내의 값을 생성합니다[0, RAND_MAX]
.
C11 표준에서 인용 :
이 srand
함수는 인수를 시드로 사용하여에 대한 후속 호출에 의해 반환되는 새로운 의사 난수 시퀀스를 생성합니다 rand
. srand
그런 다음 동일한 시드 값으로 호출 되면
의사 난수 시퀀스가 반복됩니다. 경우 rand
모든 호출을하기 전에 호출 srand
되었을 동일한 순서로 생성 될 때한다 srand
제 1 시드 값이라고한다.
많은 사람들이 합리적으로 그 예상 rand()
범위에 반 독립적 인 균일하게 분포 된 일련의 숫자를 생산하는 것 0
까지 RAND_MAX
. 가장 확실하게 (그렇지 않으면 쓸모가 없지만) 표준은 그럴 필요 가 없습니다 . "임의의 무작위 서열의 품질에 대한 보장은 없습니다"라는 명시 적 부인도 있습니다. 일부 역사적인 경우 rand
/ srand
구현은 실제로 품질이 매우 좋지 않았습니다. 현대적인 구현에서는 충분하지만 신뢰는 깨졌고 복구하기 쉽지 않습니다. 스레드에 안전하지 않은 특성 외에도 멀티 스레드 응용 프로그램에서 안전하고 까다 롭고 제한적입니다 (아직도 하나의 전용 스레드에서 사용할 수 있음).
새로운 클래스 템플릿 std :: mersenne_twister_engine <> (및 편리한 템플릿 매개 변수 조합을 갖춘 편리한 typedef-std::mt19937
/ std::mt19937_64
)은 C ++ 11 표준에 정의 된 객체 별 의사 난수 생성기를 제공합니다. 동일한 템플릿 매개 변수와 동일한 초기화 매개 변수를 사용하면 C ++ 11 호환 표준 라이브러리로 구축 된 모든 응용 프로그램의 모든 컴퓨터에서 서로 다른 개체가 동일한 개체 별 출력 시퀀스를 생성합니다. 이 클래스의 장점은 예측 가능한 고품질 출력 시퀀스와 모든 구현에서 일관성이 있다는 것입니다.
또한 C ++ 11 표준 -std :: linear_congruential_engine <> (역사적 srand/rand
으로 일부 C 표준 라이브러리 구현에서 공정한 품질 알고리즘으로 사용됨 ) 및 std :: subtract_with_carry_engine <>에 정의 된 PRNG 엔진이 더 있습니다. 또한 완전히 정의 된 매개 변수 종속 오브젝트 별 출력 시퀀스를 생성합니다.
위의 쓸모없는 C 코드에 대한 현대 C ++ 11 대체 예제 :
#include <iostream>
#include <chrono>
#include <random>
int main()
{
std::random_device rd;
// seed value is designed specifically to make initialization
// parameters of std::mt19937 (instance of std::mersenne_twister_engine<>)
// different across executions of application
std::mt19937::result_type seed = rd() ^ (
(std::mt19937::result_type)
std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch()
).count() +
(std::mt19937::result_type)
std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::high_resolution_clock::now().time_since_epoch()
).count() );
std::mt19937 gen(seed);
for( unsigned long j = 0; j < 100500; ++j )
/* ^^^Yes. Generating single pseudo-random number makes no sense
even if you use std::mersenne_twister_engine instead of rand()
and even when your seed quality is much better than time(NULL) */
{
std::mt19937::result_type n;
// reject readings that would make n%6 non-uniformly distributed
while( ( n = gen() ) > std::mt19937::max() -
( std::mt19937::max() - 5 )%6 )
{ /* bad value retrieved so get next one */ }
std::cout << n << '\t' << n % 6 + 1 << '\n';
}
return 0;
}
std :: uniform_int_distribution <> 을 사용하는 이전 코드 버전
#include <iostream>
#include <chrono>
#include <random>
int main()
{
std::random_device rd;
std::mt19937::result_type seed = rd() ^ (
(std::mt19937::result_type)
std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch()
).count() +
(std::mt19937::result_type)
std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::high_resolution_clock::now().time_since_epoch()
).count() );
std::mt19937 gen(seed);
std::uniform_int_distribution<unsigned> distrib(1, 6);
for( unsigned long j = 0; j < 100500; ++j )
{
std::cout << distrib(gen) << ' ';
}
std::cout << '\n';
return 0;
}