범위에서 임의의 정수 생성


157

주어진 범위 (국경 값 포함)에서 임의의 정수를 생성하는 함수가 필요합니다. 나는 불합리한 품질 / 무작위 요구 사항이 아니며 다음 네 가지 요구 사항이 있습니다.

  • 나는 그것을 빨리해야합니다. 내 프로젝트는 수백만 (또는 때로는 수천만)의 난수를 생성해야하며 현재 생성기 기능은 병목 현상으로 입증되었습니다.
  • 나는 합리적으로 균일해야합니다 (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의 비율이 표시됩니다.

더 나은 공식을 제안 해 주시겠습니까? (또는 전체 의사 난수 생성기 기능)



3
@Bill MaGriff : 예. 같은 문제가 있습니다. 간단한 버전은 다음과 같습니다. 사탕을 끊지 않고 10 명의 사탕을 3 명의 아이들 사이에 균등하게 나눌 수 있습니까? 대답은 할 수 없다는 것입니다. 각 어린이에게 3 개를 주어야하고, 10 번째 아이에게 아무 것도주지 말아야합니다.
Jerry Coffin

5
Boost.Random을 보셨습니까 ?
Fred Nurk

3
Andrew Koenig 기사 "거의 제대로 해결되지 않은 간단한 문제"를 확인하십시오. drdobbs.com/blog/archives/2010/11/a_simple_proble.html
Gene Bushuyev

1
@Gene Bushuyev : Andrew와 저는이 주제에 대해 꽤 오랫동안 노력해 왔습니다. groups.google.com/group/comp.lang.c++/browse_frm/thread/…groups.google.com/group/comp.os.ms-windows.programmer.tools.mfc/…
Jerry Coffin을

답변:


105

귀하의 것보다 빠르지 만 다소 우수하지만 여전히 균등 한 분산 솔루션은

output = min + (rand() % static_cast<int>(max - min + 1))

범위의 크기가 2의 거듭 제곱 인 경우를 제외 하고이 방법은 의 품질에 관계없이 치우친 비 균일 분포 수를 생성합니다rand() . 이 방법의 품질에 대한 종합적인 테스트는 다음을 참조 하십시오 .


2
고마워, 이것은 빠른 테스트에서 나에게 충분할 것 같습니다 -1, 0, 1의 분포는 거의 33:33:33입니다.
Matěj Zábský

3
항상 최대 값을 반환합니다. 여기에 뭔가 빠졌습니까? : |
rohan-patel

15
rand()C ++에서 유해한 것으로 간주되어야합니다 . 균일하게 분배되고 실제로 임의의 것을 얻는 더 좋은 방법이 있습니다.
Mgetz

1
실제로 시간의 100 % 범위 내에서 올바른 숫자를 반환합니까? 나는 "올바른 방법"그것을 할 재귀를 사용하고 여기에 다른 유래의 답을 발견했습니다 stackoverflow.com/a/6852396/623622
Czarek Tomczak을

2
많은 독자들이 신뢰할 수있는 정보원으로 보이는 고도로 호의적 인 답변이므로이 솔루션의 품질과 잠재적 위험에 대해 언급하는 것이 매우 중요하다고 생각합니다.
plasmacel

296

가장 간단한 (그리고 최상의) 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);

바퀴를 다시 발명 할 필요가 없습니다. 편견에 대해 걱정할 필요가 없습니다. 임의의 시드로 시간을 사용하는 것에 대해 걱정할 필요가 없습니다.


1
요즘 이것이 이되어야합니다 . 더 많은 기능에 대한 의사 난수 생성 참조 .
alextoind

8
나는 "최고"가 아니라 "가장 단순한"(그리고 가장 관용적 인)에 동의합니다. 불행히도이 표준은 어떠한 보증도하지 않으며 어떤 경우에는random_device 완전히 손상 될 수도 있습니다 . 더구나, 범용성이 매우 우수하지만 양질의 발전기 중에서 가장 빠르지는 않으므로 ( 이 비교 참조 ) OP에 이상적인 후보가 아닐 수도 있습니다. mt19937
Alberto M

1
@AlbertoM 불행히도, 당신이 언급 한 비교는 충분한 세부 사항을 제공하지 못하고 재현 할 수 없으므로 모호하게 만듭니다 (또한 2015 년이며 내 대답은 2013 년으로 거슬러 올라갑니다). 주변에 더 나은 방법이 있다는 것은 사실 일 minstd것입니다. (그리고 앞으로는 그러한 방법이 되길 바랍니다 ), 그것은 진전입니다. 구현이 random_device잘못되면 끔찍하며 버그로 간주해야합니다 (허용되는 경우 C ++ 표준의 가능성도 있음).
Walter

1
전적으로 동의합니다; 나는 실제로 솔루션 자체 를 비판하고 싶지 않았으며 C ++ 11의 약속에도 불구 하고이 문제에 대한 결정적인 대답은 아직 작성되지 않았다고 캐주얼 독자에게 경고하고 싶었습니다. 관련 질문에 대한 답변으로 2015 년 현재 주제에 대한 개요를 게시하려고합니다 .
Alberto M

1
"가장 간단하다"? 명확하게 훨씬 더 단순한 rand()옵션이 아닌 이유를 자세히 설명 하고 랜덤 피벗 인덱스 생성과 같이 중요하지 않은 용도에 중요합니까? 또한 타이트 루프 / 인라인 함수에서 random_device/ mt19937/ 생성에 대해 걱정해야 uniform_int_distribution합니까? 차라리 통과하는 것을 선호해야합니까?
bluenote10

60

컴파일러가 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_접두사는 "예상"을 나타냄)


3
이 답변은 간단한 요약 코드 스 니펫을 사용하여 범위에서 임의의 정수를 생성하는 데 실제로 필요한 코드 만 보여줍니다.
arekolek

분포의 최소값과 최대 값이 변하지 않기 때문에 문제가 더 쉬워집니다. d다른 범위로 반복 할 때마다 생성해야한다면 어떻게해야 합니까? 루프가 얼마나 느려질까요?
quant_dev

15

문제를 두 부분으로 나누겠습니다.

  • n0에서 (max-min) 범위 의 난수를 생성하십시오 .
  • 해당 번호에 분을 추가

첫 번째 부분은 분명히 가장 어렵다. rand ()의 반환 값이 완전히 균일하다고 가정 해 봅시다. 모듈로를 사용하면 첫 번째 (RAND_MAX + 1) % (max-min+1)숫자에 편향이 추가 됩니다. 우리가 마법으로 변경 될 가능성이있는 경우에 그래서 RAND_MAXRAND_MAX - (RAND_MAX + 1) % (max-min+1), 더 이상 편견이 없을 것이다.

의사 비결정론을 알고리즘의 실행 시간으로 기꺼이 허용하려는 경우이 직감을 사용할 수 있습니다. rand ()가 너무 큰 숫자를 반환 할 때마다, 우리는 충분히 작은 숫자를 얻을 때까지 다른 임의의 숫자를 요청합니다.

주행 시간은 지금이다 기하학적으로 분산 예상되는 값으로, 첫 번째 시도에 충분히 작은 수를 얻기의 확률이다. 때문에 항상 미만이며 , 우리는 그것을 알고 반복의 예상 수는 항상 적은 어떤 범위이보다 수 있도록. 이 기술을 사용하여 표준 CPU에서 1 초 이내에 수천만 개의 난수를 생성 할 수 있어야합니다.1/ppRAND_MAX - (RAND_MAX + 1) % (max-min+1)(RAND_MAX + 1) / 2p > 1/2

편집하다:

위의 내용은 기술적으로 정확하지만 실제로 DSimon의 답변이 더 유용 할 것입니다. 이 물건을 직접 구현해서는 안됩니다. 나는 거부 샘플링의 많은 구현을 보았고 그것이 올바른지 아닌지를 확인하기가 종종 어렵습니다.


완전성 : 이것은 거부 샘플링 입니다.
etarion 2019

3
재미있는 사실 : Joel Spolsky는이 질문의 버전을 StackOverflow가 응답하기에 좋은 예라고 언급 한 적이 있습니다. 나는 그 시간에 사이트와 관련된 거부 샘플링에 대한 답변을 통해 보면서 모든 하나 하나가 잘못되었습니다.
Jørgen Fogh

13

방법에 대한 메르 센 트위스터 ? 부스트 구현은 사용하기가 쉬우 며 많은 실제 응용 프로그램에서 잘 테스트됩니다. 인공 지능 및 진화 알고리즘과 같은 여러 학술 프로젝트에서 직접 사용했습니다.

다음은 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의 매력적인 측면은 숫자를 생성하기 위해 시간이 많이 걸리는 곱셈이 아닌 이진 연산을 사용한다는 것입니다. 이 알고리즘은 기간이 길고 세분성이 우수합니다. 비 암호화 응용 프로그램에 빠르고 효과적입니다.


1
메르 센 트위스터는 좋은 발전기이지만, 그가 다루는 문제는 기본 발전기 자체에 관계없이 남아 있습니다.
Jerry Coffin

(임의의 프로젝트는 라이브러리이므로) 프로젝트에 다른 종속성을 도입한다는 것을 의미하기 때문에 임의 생성기에 대해서만 Boost를 사용하고 싶지 않습니다. 어쨌든 나중에 사용해야 할 것이므로이 발전기로 전환 할 수 있습니다.
Matěj Zábský

1
@ 제리 관 어떤 문제? 그의 요구 사항을 모두 충족했기 때문에 제공했습니다. 빠르고 균일합니다 ( boost::uniform_int분포 사용). 최소 최대 범위를 원하는대로 변환하고 시드 할 수 있습니다.
Aphex

@mzabsky 아마 교수님에게 프로젝트를 제출하기 위해 교수님에게 보내야 할 때, 제가 사용하고있는 관련 부스트 헤더 파일을 포함 시켰습니다. 전체 40MB 부스트 라이브러리를 코드와 함께 패키지하지 않아도됩니다. 물론 당신의 경우에 이것은 저작권과 같은 다른 이유로 가능하지 않을 수도 있습니다 ...
Aphex

@Aphex 내 프로젝트 는 실제로 과학적인 시뮬레이터가 아니거나 균일 한 분포가 필요한 것이 아닙니다. 나는 1.5 년 동안 아무런 문제없이 오래된 발전기를 사용했는데, 매우 작은 범위 (이 경우 3)에서 숫자를 생성하기 위해 처음 필요할 때 바이어스 된 분포 만 보았습니다. 부스트 솔루션을 고려할 때 속도는 여전히 논쟁의 여지가 있습니다. 라이센스를 조사하여 필요한 몇 가지 파일을 프로젝트에 추가 할 수 있는지 확인합니다. "Checkout-> F5-> ready to use"가 마음에 듭니다.
Matěj Zábský

11
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 () 결과를 무시해야합니다.


인기가 없지만, 비과학 프로젝트에도 사용합니다. 이해하기 쉽고 (수학 학위가 필요하지 않음) 적절하게 수행합니다 (이를 사용하여 코드를 프로파일 링하지 않아도 됨). :) 넓은 범위의 경우 두 개의 rand () 값을 함께 묶어 30 비트 값을 얻을 수 있다고 생각합니다 (RAND_MAX = 0x7fff, 즉 15 개의 랜덤 비트로 가정)
efotinis

변화 RAND_MAX하는 (double) RAND_MAX오버 플로우 경고 정수 방지 할 수 있습니다.
alex

4

다음은 숫자를 생성하는 바이어스되지 않은 버전입니다 [low, high].

int r;
do {
  r = rand();
} while (r < ((unsigned int)(RAND_MAX) + 1) % (high + 1 - low));
return r % (high + 1 - low) + low;

범위가 상당히 작은 경우 do루프 에서 비교의 오른쪽을 캐시 할 이유가 없습니다 .


IMO, 거기에 제시된 솔루션 중 어느 것도 실제로 많은 개선이 없습니다. 그의 루프 기반 솔루션은 작동하지만 특히 OP와 같은 소규모 범위에서는 비효율적 일 수 있습니다. 그의 균일 한 편차 솔루션은 실제로 균일 한 편차를 전혀 생성하지 않습니다 . 기껏해야 그것은 일종의 균일 성이 결여되어 있습니다.
Jerry Coffin

@ 제리 : 새 버전을 확인하십시오.
예레미야 윌콕

나는 그것이 올바르게 작동하는지 조금 불확실합니다. 적어도 나에게는 정확성이 분명하지 않은 것 같습니다.
Jerry Coffin

@ 제리 : 여기 내 추론이 있습니다 : 범위가 [0, h)단순 하다고 가정합니다 . 호출 rand()에는 RAND_MAX + 1가능한 반환 값이 있습니다. 복용 rand() % h붕괴 (RAND_MAX + 1) / h의 각각을 그들의 h것을 제외 출력값 (RAND_MAX + 1) / h + 1그 미만으로되는 값으로 매핑된다 (RAND_MAX + 1) % h(관통 때문에 마지막 부분 사이클의 h출력). 따라서 (RAND_MAX + 1) % h편향되지 않은 분포를 얻기 위해 가능한 출력을 제거 합니다.
Jeremiah Willcock


1

min과 max가 int 값이라고 가정하고, [와]는이 값을 포함하고, (와)는 위 값을 사용하여 c ++ rand ()를 사용하여 올바른 값을 얻는 것을 의미합니다.

참조 : for () [] 정의, 방문 :

https://en.wikipedia.org/wiki/Interval_(mathematics)

rand 및 srand 함수 또는 RAND_MAX 정의의 경우 다음을 방문하십시오.

http://en.cppreference.com/w/cpp/numeric/random/rand

[최소 최대]

int randNum = rand() % (max - min + 1) + min

(최소 최대]

int randNum = rand() % (max - min) + min + 1

[최소 최대)

int randNum = rand() % (max - min) + min

(최소 최대)

int randNum = rand() % (max - min - 1) + min + 1

0

이 스레드 거부 샘플링에서 이미 논의했지만 rand() % 2^something이미 위에서 언급 한 바와 같이 바이어스가 발생하지 않는다는 사실을 기반으로 한 최적화를 제안하고 싶습니다 .

알고리즘은 정말 간단합니다 :

  • 구간 길이보다 2의 가장 작은 거듭 제곱을 계산
  • "새"간격에서 하나의 숫자를 무작위로
  • 원래 간격의 길이보다 작은 경우 해당 숫자를 반환
    • 그렇지 않으면 거절하다

내 샘플 코드는 다음과 같습니다.

int randInInterval(int min, int max) {
    int intervalLen = max - min + 1;
    //now calculate the smallest power of 2 that is >= than `intervalLen`
    int ceilingPowerOf2 = pow(2, ceil(log2(intervalLen)));

    int randomNumber = rand() % ceilingPowerOf2; //this is "as uniform as rand()"

    if (randomNumber < intervalLen)
        return min + randomNumber;      //ok!
    return randInInterval(min, max);    //reject sample and try again
} 

2의 거듭 제곱이 실제 간격 길이보다 "가까워 지므로"누락 수가 적기 때문에 특히 작은 간격에 적합합니다.

추신 :
재귀를 피하는 것이 더 효율적 일 것입니다 (로그 한도 이상을 계산할 필요가 없습니다.). 그러나 나는이 예제에서 더 읽기 쉽다고 생각했습니다.


0

대부분의 제안에서 일반적으로 0에서 RAND_MAX에 이르는 rand () 함수에서 얻은 초기 임의 값은 단순히 낭비됩니다. 당신은 더 많은 것을 줄 수있는 건전한 절차가있는 동안 하나의 난수 만 생성하고 있습니다.

정수 난수의 [min, max] 영역을 원한다고 가정하십시오. [0, max-min]부터 시작합니다

기본 b = max-min + 1

b의 rand ()에서 얻은 숫자를 나타내는 것으로 시작하십시오.

그렇게하면 밑 (b)의 각 숫자 (마지막 숫자를 제외하고)는 [0, max-min] 범위의 난수를 나타내므로 floor (log (b, RAND_MAX))가됩니다.

물론 [min, max] 로의 최종 이동은 각 난수 r + min에 대해 간단합니다.

int n = NUM_DIGIT-1;
while(n >= 0)
{
    r[n] = res % b;
    res -= r[n];
    res /= b;
    n--;
}

NUM_DIGIT가베이스 b에서 추출 할 수있는 자릿수 인 경우

NUM_DIGIT = floor(log(b,RAND_MAX))

위의 내용은 b <RAND_MAX를 제공하는 하나의 RAND_MAX 난수에서 0에서 b-1까지 NUM_DIGIT 난수를 추출하는 간단한 구현입니다.


-1

이에 대한 공식은 매우 간단하므로이 표현을 사용해보십시오.

 int num = (int) rand() % (max - min) + min;  
 //Where rand() returns a random number between 0.0 and 1.0

2
전체 문제는 런타임에 지정된 범위에서 정수를 반환하는 C / C ++의 랜드를 사용하는 것이 었습니다. 이 스레드에서 설명했듯이 통계 속성이나 성능을 손상시키지 않으려면 [0, RAND_MAX]에서 [MIN, MAX]로 임의의 정수를 매핑하는 것이 완전히 간단하지 않습니다. [0, 1] 범위의 배가 있으면 매핑이 쉽습니다.
Matěj Zábský

2
당신의 대답이 틀 렸습니다, 대신 계수를 사용해야합니다 :int num = (int) rand() % (max - min) + min;
Jaime Ivan Cervantes

-2

내가 실수하지 않으면 다음 표현은 편견이 없어야합니다.

std::floor( ( max - min + 1.0 ) * rand() ) + min;

여기서 rand ()는 0.0을 포함하지 않고 0.0과 1.0 사이의 임의의 값을 1.0을 포함하지 않으며 max와 min은 min <max. 인 조건의 정수라고 가정합니다.


std::floor를 반환 double하고 여기에 정수 값이 필요합니다. 나는 int대신을 사용하여 캐스팅했습니다 std::floor.
musiphil
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.