주어진 범위 (국경 값 포함)에서 임의의 정수를 생성하는 함수가 필요합니다. 나는 불합리한 품질 / 무작위 요구 사항이 아니며 다음 네 가지 요구 사항이 있습니다.
- 나는 그것을 빨리해야합니다. 내 프로젝트는 수백만 (또는 때로는 수천만)의 난수를 생성해야하며 현재 생성기 기능은 병목 현상으로 입증되었습니다.
- 나는 합리적으로 균일해야합니다 (rand () 사용은 완벽하게 좋습니다).
- 최소-최대 범위는 <0, 1>에서 <-32727, 32727>까지 가능합니다.
- 시드 가능해야합니다.
현재 다음 C ++ 코드가 있습니다.
output = min + (rand() * (int)(max - min) / RAND_MAX)
문제는 실제로 균일하지 않다는 것입니다 .max는 rand () = RAND_MAX (Visual C ++의 경우 1/32727) 일 때만 반환됩니다. 이것은 마지막 값이 거의 반환되지 않는 <-1, 1>과 같은 작은 범위의 주요 문제입니다.
그래서 펜과 종이를 잡고 다음 공식 ((int) (n + 0.5) 정수 반올림 트릭을 기반으로 함)을 생각해 냈습니다.
그러나 여전히 나에게 균일 한 분포를 제공하지는 않습니다. 10000 개의 샘플을 반복 실행하면 값 값 -1, 0에 대해 37:50:13의 비율이 표시됩니다.
더 나은 공식을 제안 해 주시겠습니까? (또는 전체 의사 난수 생성기 기능)
답변
귀하의 것보다 빠르지 만 다소 우수하지만 여전히 균등 한 분산 솔루션은
output = min + (rand() % static_cast<int>(max - min + 1))
범위의 크기가 2의 거듭 제곱 인 경우를 제외 하고이 방법은 의 품질에 관계없이 치우친 비 균일 분포 수를 생성합니다rand()
. 이 방법의 품질에 대한 종합적인 테스트는 다음을 참조 하십시오 .
답변
가장 간단한 (그리고 최상의) C ++ (2011 표준 사용) 대답은 다음과 같습니다.
#include <random>
std::random_device rd; // only used once to initialise (seed) engine
std::mt19937 rng(rd()); // random-number engine used (Mersenne-Twister in this case)
std::uniform_int_distribution<int> uni(min,max); // guaranteed unbiased
auto random_integer = uni(rng);
바퀴를 다시 발명 할 필요가 없습니다. 편견에 대해 걱정할 필요가 없습니다. 임의의 시드로 시간을 사용하는 것에 대해 걱정할 필요가 없습니다.
답변
컴파일러가 C ++ 0x를 지원하고이를 사용하는 것이 옵션이라면, 새로운 표준 <random>
헤더가 귀하의 요구를 충족시킬 것입니다. uniform_int_distribution
최소 및 최대 범위 (필요한 경우 포함)를 허용하는 고품질 을 가지고 있으며 다양한 난수 생성기 중에서 선택하여 해당 분포에 연결할 수 있습니다.
다음은 int
[-57, 365]에 균일하게 분포 된 백만 개의 난수를 생성하는 코드입니다 . <chrono>
성능이 중요한 관심사라고 언급 할 때 새로운 표준 기능을 사용하여 시간을 측정했습니다.
#include <iostream>
#include <random>
#include <chrono>
int main()
{
typedef std::chrono::high_resolution_clock Clock;
typedef std::chrono::duration<double> sec;
Clock::time_point t0 = Clock::now();
const int N = 10000000;
typedef std::minstd_rand G;
G g;
typedef std::uniform_int_distribution<> D;
D d(-57, 365);
int c = 0;
for (int i = 0; i < N; ++i)
c += d(g);
Clock::time_point t1 = Clock::now();
std::cout << N/sec(t1-t0).count() << " random numbers per second.\n";
return c;
}
나 (2.8GHz Intel Core i5)의 경우 다음과 같이 인쇄됩니다.
초당 2.10268e + 07의 난수.
생성자에 int를 전달하여 생성기를 시드 할 수 있습니다.
G g(seed);
나중에 int
배포에 필요한 범위를 포함하지 않는 것을 발견하면 다음 uniform_int_distribution
과 같이 변경하여 해결할 수 있습니다 (예 🙂 long long
.
typedef std::uniform_int_distribution<long long> D;
나중에 minstd_rand
고품질 발전기가 충분하지 않다는 것을 알게되면 쉽게 교체 할 수 있습니다. 예 :
typedef std::mt19937 G; // Now using mersenne_twister_engine
난수 생성기를 개별적으로 제어하면 난수 분포가 상당히 자유로울 수 있습니다.
또한이 분포의 첫 번째 4 개의 “모멘트”를 사용하여 (사용하지 않음) 계산 하고 분포의 품질을 정량화하기 위해 이론적 값minstd_rand
과 비교했습니다 .
min = -57
max = 365
mean = 154.131
x_mean = 154
var = 14931.9
x_var = 14910.7
skew = -0.00197375
x_skew = 0
kurtosis = -1.20129
x_kurtosis = -1.20001
( x_
접두사는 “예상”을 나타냄)
답변
문제를 두 부분으로 나누겠습니다.
n
0에서 (max-min) 범위 의 난수를 생성하십시오 .- 해당 번호에 분을 추가
첫 번째 부분은 분명히 가장 어렵다. rand ()의 반환 값이 완전히 균일하다고 가정 해 봅시다. 모듈로를 사용하면 첫 번째 (RAND_MAX + 1) % (max-min+1)
숫자에 편향이 추가 됩니다. 우리가 마법으로 변경 될 가능성이있는 경우에 그래서 RAND_MAX
에 RAND_MAX - (RAND_MAX + 1) % (max-min+1)
, 더 이상 편견이 없을 것이다.
의사 비결정론을 알고리즘의 실행 시간으로 기꺼이 허용하려는 경우이 직감을 사용할 수 있습니다. rand ()가 너무 큰 숫자를 반환 할 때마다, 우리는 충분히 작은 숫자를 얻을 때까지 다른 임의의 숫자를 요청합니다.
주행 시간은 지금이다 기하학적으로 분산 예상되는 값으로, 첫 번째 시도에 충분히 작은 수를 얻기의 확률이다. 때문에 항상 미만이며 , 우리는 그것을 알고 반복의 예상 수는 항상 적은 어떤 범위이보다 수 있도록. 이 기술을 사용하여 표준 CPU에서 1 초 이내에 수천만 개의 난수를 생성 할 수 있어야합니다.1/p
p
RAND_MAX - (RAND_MAX + 1) % (max-min+1)
(RAND_MAX + 1) / 2
p > 1/2
편집하다:
위의 내용은 기술적으로 정확하지만 실제로 DSimon의 답변이 더 유용 할 것입니다. 이 물건을 직접 구현해서는 안됩니다. 나는 거부 샘플링의 많은 구현을 보았고 그것이 올바른지 아닌지를 확인하기가 종종 어렵습니다.
답변
방법에 대한 메르 센 트위스터 ? 부스트 구현은 사용하기가 쉬우 며 많은 실제 응용 프로그램에서 잘 테스트됩니다. 인공 지능 및 진화 알고리즘과 같은 여러 학술 프로젝트에서 직접 사용했습니다.
다음은 6 면체 주사위를 굴리는 간단한 기능을 만드는 예입니다.
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_int.hpp>
#include <boost/random/variate_generator.hpp>
boost::mt19937 gen;
int roll_die() {
boost::uniform_int<> dist(1, 6);
boost::variate_generator<boost::mt19937&, boost::uniform_int<> > die(gen, dist);
return die();
}
아, 그리고 당신은 당신이 그것을 훨씬 열등하게 사용해야한다고 확신하지 못하는 경우를 대비 하여이 발전기의 포주가 있습니다 rand()
.
메르 센 트위스터는 마츠모토 마코토와 니시무라 타쿠지가 발명 한 “랜덤 넘버”생성기입니다. 그들의 웹 사이트에는 수많은 알고리즘 구현이 포함되어 있습니다.
본질적으로 Mersenne Twister는 매우 큰 선형 피드백 시프트 레지스터입니다. 이 알고리즘은 19,937 비트 시드에서 작동하며 32 비트 부호없는 정수로 구성된 624 요소 배열에 저장됩니다. 값 2 ^ 19937-1은 메르 센 소수입니다. 시드 조작 기술은 오래된 “트위스트”알고리즘을 기반으로합니다. 따라서 “Mersenne Twister”라는 이름이 사용됩니다.
Mersenne Twister의 매력적인 측면은 숫자를 생성하기 위해 시간이 많이 걸리는 곱셈이 아닌 이진 연산을 사용한다는 것입니다. 이 알고리즘은 기간이 길고 세분성이 우수합니다. 비 암호화 응용 프로그램에 빠르고 효과적입니다.
답변
int RandU(int nMin, int nMax)
{
return nMin + (int)((double)rand() / (RAND_MAX+1) * (nMax-nMin+1));
}
이것은 32768 정수를 (nMax-nMin + 1) 정수로 매핑합니다. (nMax-nMin + 1)이 작 으면 (필요한 경우) 매핑이 매우 좋습니다. 그러나 (nMax-nMin + 1)이 크면 매핑이 작동하지 않습니다 (예를 들어 32768 값을 같은 확률로 30000 값에 매핑 할 수 없음). 이러한 범위가 필요한 경우 15 비트 rand () 대신 32 비트 또는 64 비트 임의 소스를 사용하거나 범위를 벗어난 rand () 결과를 무시해야합니다.
답변
다음은 숫자를 생성하는 바이어스되지 않은 버전입니다 [low, high]
.
int r;
do {
r = rand();
} while (r < ((unsigned int)(RAND_MAX) + 1) % (high + 1 - low));
return r % (high + 1 - low) + low;
범위가 상당히 작은 경우 do
루프 에서 비교의 오른쪽을 캐시 할 이유가 없습니다 .