나는이 질문에 많은 질문을 보았지만 그것에 대한 확실한 대답은 보지 못했습니다. 그래서 여기 rand()
에 C ++에서 와 같이 난수 생성기를 사용할 때 왜 "모듈러스 편향"이 있는지 이해하는 데 도움이 될 것 입니다.
나는이 질문에 많은 질문을 보았지만 그것에 대한 확실한 대답은 보지 못했습니다. 그래서 여기 rand()
에 C ++에서 와 같이 난수 생성기를 사용할 때 왜 "모듈러스 편향"이 있는지 이해하는 데 도움이 될 것 입니다.
답변:
그래서 rand()
0 사이의 자연수를 선택하고, 의사 난수 생성기 RAND_MAX
에 정의 된 상수 인 cstdlib
(이 참조 문서 에 대한 일반적인 개요가 rand()
).
이제 0과 2 사이의 난수를 생성하려면 어떻게됩니까? 설명을 위해 RAND_MAX
10 이라고 가정 하고을 호출하여 0과 2 사이의 난수를 생성하기로 결정합니다 rand()%3
. 그러나 rand()%3
동일한 확률로 0과 2 사이의 숫자를 생성하지 않습니다!
때 rand()
반환 0, 3, 6, 9, rand()%3 == 0
. 따라서 P (0) = 4/11
때 rand()
반환 1, 4, 7, 10, rand()%3 == 1
. 따라서 P (1) = 4/11
때 rand()
2, 5, 8을 반환합니다 rand()%3 == 2
. 따라서 P (2) = 3/11
이것은 동일한 확률로 0과 2 사이의 숫자를 생성하지 않습니다. 물론 작은 범위의 경우 가장 큰 문제는 아니지만 큰 범위의 경우 분포가 왜곡되어 더 작은 숫자를 바이어스 할 수 있습니다.
그렇다면 rand()%n
같은 확률로 0에서 n-1 사이의 숫자 범위를 언제 반환합니까? 언제 RAND_MAX%n == n - 1
. 이 경우, 이전 가정과 함께 rand()
0 RAND_MAX
에서 같은 확률 로 숫자를 반환하므로 n의 모듈로 클래스도 똑같이 분포됩니다.
이 문제를 어떻게 해결할 수 있습니까? 원하는 방법은 원하는 범위의 숫자를 얻을 때까지 난수를 계속 생성하는 것입니다.
int x;
do {
x = rand();
} while (x >= n);
하지만 값이 낮은 n
경우에는 비효율적입니다. n/RAND_MAX
범위 내 에서만 값을 얻을 수 있으므로 평균적으로 RAND_MAX/n
통화를 수행해야합니다 rand()
.
보다 효율적인 식 접근 방식에 의해 길이 나눌와 일부 대형 범위를 가지고하는 것 n
같은, RAND_MAX - RAND_MAX % n
당신이 범위에 있다고 하나를 얻을, 다음 계수를 취할 때까지 임의의 숫자를 생성 유지 :
int x;
do {
x = rand();
} while (x >= (RAND_MAX - RAND_MAX % n));
x %= n;
값이 작은 경우에 대한 n
호출이 두 번 이상 필요하지 않습니다 rand()
.
인용 및 추가 자료 :
무작위를 계속 선택하는 것은 편견을 제거하는 좋은 방법입니다.
최신 정보
범위를 x로 나눌 수있는 x를 검색하면 코드를 빠르게 만들 수 n
있습니다.
// Assumptions
// rand() in [0, RAND_MAX]
// n in (0, RAND_MAX]
int x;
// Keep searching for an x in a range divisible by n
do {
x = rand();
} while (x >= RAND_MAX - (RAND_MAX % n))
x %= n;
위의 루프는 매우 빠르므로 평균적으로 1 회 반복해야합니다.
rand()
반환 할 수있는 값의 수가 배수가 아닌 경우 n
수행하는 모든 값을 무시하지 않으면 불가피하게 '모듈러스 바이어스'를 얻습니다. user1413793은 잘 설명합니다 (해답에서 제안 된 솔루션은 정말 유쾌하지만).
RAND_MAX+1 - (RAND_MAX+1) % n
올바르게 작동하지만 RAND_MAX+1 - ((RAND_MAX+1) % n)
명확성 을 위해 작성해야한다고 생각합니다 .
RAND_MAX == INT_MAX
(대부분의 시스템에서와 같이) 작동 하지 않습니다 . 위의 @ user1413793에 대한 두 번째 주석을 참조하십시오.
문제에 대해서는 @ user1413793이 맞습니다. 한 가지 점을 제외하고는 더 이상 논의하지 않을 것입니다. 예, 작은 값 n
과 큰 값의 RAND_MAX
경우 모듈러스 바이어스가 매우 작을 수 있습니다. 그러나 바이어스 유도 패턴을 사용한다는 것은 임의의 숫자를 계산할 때마다 바이어스를 고려해야하고 경우에 따라 다른 패턴을 선택해야한다는 것을 의미합니다. 그리고 만약 당신이 잘못된 선택을한다면, 그것이 도입 한 버그는 미묘하고 단위 테스트가 거의 불가능합니다. 적절한 도구 (예 :)를 사용하는 것과 비교할 때 arc4random_uniform
, 이는 더 적은 작업이 아니라 추가 작업입니다. 더 많은 작업을하고 더 나쁜 솔루션을 얻는 것은 끔찍한 엔지니어링입니다. 특히 대부분의 플랫폼에서 매번 작업을 수행 할 때 특히 그렇습니다.
불행히도, 솔루션의 구현은 모두 잘못되었거나 덜 효율적입니다. (각 솔루션에는 문제를 설명하는 다양한 의견이 있지만 문제를 해결하기 위해 해결 된 솔루션은 없습니다.) 이는 일반적인 답을 찾는 사람을 혼란스럽게 만들 가능성이 높으므로 여기서 잘 알려진 구현을 제공하고 있습니다.
다시 말하지만 최상의 솔루션은 arc4random_uniform
해당 플랫폼을 제공하는 플랫폼 또는 플랫폼과 유사한 범위의 솔루션 (예 : Random.nextInt
Java)에서 사용하는 것입니다. 코드 비용없이 올바른 작업을 수행합니다. 이것은 거의 항상 올바른 전화입니다.
이없는 경우 arc4random_uniform
오픈 소스의 힘을 사용하여 더 넓은 범위의 RNG 위에 어떻게 구현되는지 확인할 수 있습니다 ( ar4random
이 경우 비슷한 방법이 다른 RNG에서도 작동 할 수 있음).
OpenBSD 구현 은 다음과 같습니다 .
/*
* Calculate a uniformly distributed random number less than upper_bound
* avoiding "modulo bias".
*
* Uniformity is achieved by generating new random numbers until the one
* returned is outside the range [0, 2**32 % upper_bound). This
* guarantees the selected random number will be inside
* [2**32 % upper_bound, 2**32) which maps back to [0, upper_bound)
* after reduction modulo upper_bound.
*/
u_int32_t
arc4random_uniform(u_int32_t upper_bound)
{
u_int32_t r, min;
if (upper_bound < 2)
return 0;
/* 2**32 % x == (2**32 - x) % x */
min = -upper_bound % upper_bound;
/*
* This could theoretically loop forever but each retry has
* p > 0.5 (worst case, usually far better) of selecting a
* number inside the range we need, so it should rarely need
* to re-roll.
*/
for (;;) {
r = arc4random();
if (r >= min)
break;
}
return r % upper_bound;
}
비슷한 것을 구현 해야하는 사람들을 위해이 코드에 대한 최신 커밋 주석에 주목할 가치가 있습니다.
계산에 변경 arc4random_uniform ()
2**32 % upper_bound
등-upper_bound % upper_bound
. 코드를 단순화하고 ILP32 및 LP64 아키텍처에서 동일하게하고 64 비트 나머지 대신 32 비트 나머지를 사용하여 LP64 아키텍처에서 약간 더 빠릅니다.Jorden Verwer가 tech @ ok deraadt; djm 또는 otto에 대한 반대 의견 없음
Java 구현도 쉽게 찾을 수 있습니다 (이전 링크 참조).
public int nextInt(int n) {
if (n <= 0)
throw new IllegalArgumentException("n must be positive");
if ((n & -n) == n) // i.e., n is a power of 2
return (int)((n * (long)next(31)) >> 31);
int bits, val;
do {
bits = next(31);
val = bits % n;
} while (bits - val + (n-1) < 0);
return val;
}
arcfour_random()
실제로 구현에 실제 RC4 알고리즘을 사용 하는 경우 출력에 약간의 편차가 있습니다. 라이브러리 작성자가 동일한 인터페이스에서 더 나은 CSPRNG를 사용하도록 전환했으면합니다. 필자는 BSD 중 하나가 실제로 ChaCha20 알고리즘을 사용하여 구현한다고 생각합니다 arcfour_random()
. 더는 쓸모 보안 또는 비디오 포커와 같은 다른 중요한 애플리케이션을위한 렌더링 RC4 출력 편견에 : blog.cryptographyengineering.com/2013/03/...
/dev/random
, 과거에는 일부 플랫폼에서 RC4를 사용했습니다 (리눅스는 카운터 모드에서 SHA-1을 사용합니다). 불행히도 검색을 통해 찾은 매뉴얼 페이지는 RC4가 제공하는 다양한 플랫폼에서 여전히 사용되고 있음을 나타냅니다 arc4random
(실제 코드는 다를 수 있음).
-upper_bound % upper_bound == 0
??
-upper_bound % upper_bound
은 int
32 비트보다 넓은 경우 실제로 0이됩니다 . 이어야합니다 (u_int32_t)-upper_bound % upper_bound)
( u_int32_t
에 대한 BSD-ism 이라고 가정 uint32_t
).
모듈로 바이어스 는 모듈로 산술을 사용하여 출력 세트를 입력 세트의 서브 세트로 줄이는 고유의 바이어스입니다. 일반적으로, 출력 세트의 크기가 입력 세트의 크기의 제수가 아닌 경우 모듈로 산술을 사용하는 경우와 같이 입력과 출력 세트 사이의 매핑이 균등하게 분포되지 않을 때마다 바이어스가 존재합니다.
이 바이어스는 숫자가 비트 열 (0과 1)로 표현되는 컴퓨팅에서 특히 피하기 어렵습니다. 무작위로 무작위로 무작위를 찾는 것도 매우 어렵지만이 논의의 범위를 벗어납니다. 이 답변의 나머지 부분에서는 실제로 임의의 비트의 무제한 소스가 있다고 가정하십시오.
이 임의의 비트를 사용하여 다이 롤 (0-5)을 시뮬레이션하는 것을 고려해 봅시다. 6 가지 가능성이 있으므로 숫자 6을 나타내는 충분한 비트가 필요합니다. 3은 3 비트입니다. 불행하게도, 3 개의 랜덤 비트는 8 가지 가능한 결과를 산출합니다.
000 = 0, 001 = 1, 010 = 2, 011 = 3
100 = 4, 101 = 5, 110 = 6, 111 = 7
우리는 모듈로 6 값을 취함으로써 결과 세트의 크기를 정확히 6으로 줄일 수 있습니다. 그러나 이것은 모듈로 바이어스 문제를 나타냅니다 : 110
0을 111
산출하고 1을 산출합니다 .
이론 상으로는 임의의 비트에 의존하는 대신 작은 군대를 고용하여 하루 종일 주사위를 굴려 결과를 데이터베이스에 기록한 다음 각 결과를 한 번만 사용할 수 있습니다. 이것은 소리만큼이나 실용적이며, 어쨌든 진정한 임의의 결과를 얻지 못할 것입니다.
대신 계수를 사용하는 순진하지만 수학적으로 올바른 해결책은 폐기 결과 그 수율입니다 110
그리고 111
단순히 3 개의 새 비트 다시 시도하십시오. 불행히도, 이것은 각 롤마다 재롤을 포함하여 재롤이 필요할 확률 이 25 % 라는 것을 의미합니다 . 이것은 가장 사소한 용도를 제외하고는 모두 비현실적입니다.
더 많은 비트를 사용하십시오. 3 비트 대신 4를 사용하십시오. 16 개의 가능한 결과가 나옵니다. 물론 결과가 5보다 클 때마다 다시 롤링하면 상황이 더 나빠져 (10/16 = 62.5 %) 혼자서 도움이되지 않습니다.
2 * 6 = 12 <16이므로, 12보다 작은 결과를 안전하게 취하고 결과를 균등하게 분배하기 위해 모듈로 6을 줄일 수 있습니다. 다른 4 개의 결과는 버리고 이전 방법과 같이 다시 롤업해야합니다.
처음에는 잘 들리지만 수학을 확인해 봅시다.
4 discarded results / 16 possibilities = 25%
이 경우 1 비트가 전혀 도움이되지 않았습니다 !
결과는 불행하지만 5 비트로 다시 시도해 보겠습니다.
32 % 6 = 2 discarded results; and
2 discarded results / 32 possibilities = 6.25%
확실한 개선이지만 많은 실제 사례에서는 충분하지 않습니다. 좋은 소식은 더 많은 비트를 추가해도 버리고 다시 굴려야 할 가능성이 높아지지 않는다는 것 입니다. 이것은 주사위뿐만 아니라 모든 경우에 적용됩니다.
그러나 입증 된 바와 같이 1 비트를 추가해도 아무런 변화가 없습니다. 실제로 롤을 6 비트로 늘리더라도 확률은 6.25 %입니다.
이것은 두 가지 추가 질문을 제기합니다.
고맙게도 첫 번째 질문에 대한 대답은 '예'입니다. 6의 문제는 2 ^ x mod 6이 2와 4 사이에서 우연히 서로 2의 배수로 바뀌어 짝수 x> 1 인 경우,
[2^x mod 6] / 2^x == [2^(x+1) mod 6] / 2^(x+1)
따라서 6은 규칙이 아니라 예외입니다. 같은 방식으로 연속 2의 거듭 제곱을 산출하는 더 큰 모듈러스를 찾을 수 있지만, 결국 랩핑해야하며 폐기 확률이 줄어 듭니다.
추가 증거를 제공하지 않으면 일반적으로 필요한 비트 수의 두 배를 사용 하면 더 작고 일반적으로 중요하지 않은 폐기 기회가 제공됩니다.
다음은 OpenSSL의 libcrypo를 사용하여 임의 바이트를 제공하는 예제 프로그램입니다. 컴파일 할 때 -lcrypto
대부분의 사람들이 사용할 수 있는 라이브러리에 연결하십시오 .
#include <iostream>
#include <assert.h>
#include <limits>
#include <openssl/rand.h>
volatile uint32_t dummy;
uint64_t discardCount;
uint32_t uniformRandomUint32(uint32_t upperBound)
{
assert(RAND_status() == 1);
uint64_t discard = (std::numeric_limits<uint64_t>::max() - upperBound) % upperBound;
uint64_t randomPool = RAND_bytes((uint8_t*)(&randomPool), sizeof(randomPool));
while(randomPool > (std::numeric_limits<uint64_t>::max() - discard)) {
RAND_bytes((uint8_t*)(&randomPool), sizeof(randomPool));
++discardCount;
}
return randomPool % upperBound;
}
int main() {
discardCount = 0;
const uint32_t MODULUS = (1ul << 31)-1;
const uint32_t ROLLS = 10000000;
for(uint32_t i = 0; i < ROLLS; ++i) {
dummy = uniformRandomUint32(MODULUS);
}
std::cout << "Discard count = " << discardCount << std::endl;
}
대부분의 조건에서 실제로 얼마나 많은 재롤이 발생하는지 확인 하려면 MODULUS
및 ROLLS
값을 사용하는 것이 좋습니다 . 회의론자는 계산 된 값을 파일에 저장하고 분포가 정상적으로 나타나는지 확인할 수 있습니다.
randomPool = RAND_bytes(...)
라인은 항상 발생합니다 randomPool == 1
주장 때문. 이로 인해 항상 폐기와 재롤이 발생합니다. 별도의 줄에 선언하고 싶다고 생각합니다. 결과적으로 이로 인해 RNG가 1
반복 될 때마다 리턴됩니다 .
모듈로 사용에 대한 두 가지 일반적인 불만이 있습니다.
하나는 모든 발전기에 유효합니다. 제한적인 경우 더보기 쉽습니다. 제너레이터에 RAND_MAX가 2이고 (C 표준과 호환되지 않음) 값으로 0 또는 1 만 원하는 경우 모듈로를 사용하면 0보다 2 배 더 자주 제너레이터가 생성됩니다 (제너레이터가 0과 2를 생성 할 때) 1을 생성하십시오 (생성기가 1을 생성 할 때). 생성기 값에서 원하는 값으로 매핑하는 것이 무엇이든 관계없이 값을 삭제하지 않는 즉시 적용됩니다.
어떤 종류의 발전기는 적어도 일부 매개 변수에 대해 다른 비트보다 덜 중요한 비트를 덜 무작위로 가지고 있지만 슬프게도 이러한 매개 변수에는 다른 흥미로운 특성이 있습니다 (예 : RAND_MAX는 2의 거듭 제곱보다 작습니다). 문제는 잘 알려져 있으며 장시간 라이브러리 구현에서는 문제를 피할 수 있습니다 (예 : C 표준의 샘플 rand () 구현은 이러한 종류의 생성기를 사용하지만 16 비트는 중요하지 않습니다). 그리고 당신은 불운을 가질 수 있습니다
같은 것을 사용하여
int alea(int n){
assert (0 < n && n <= RAND_MAX);
int partSize =
n == RAND_MAX ? 1 : 1 + (RAND_MAX-n)/(n+1);
int maxUsefull = partSize * n + (partSize-1);
int draw;
do {
draw = rand();
} while (draw > maxUsefull);
return draw/partSize;
}
0과 n 사이의 난수를 생성하면 두 가지 문제를 피할 수 있습니다 (RAND_MAX == INT_MAX로 오버플로 방지)
BTW, C ++ 11은 rand () 이외의 축소 및 기타 생성기에 표준 방법을 도입했습니다.
Mark 's Solution (허용 된 솔루션)은 거의 완벽합니다.
int x; do { x = rand(); } while (x >= (RAND_MAX - RAND_MAX % n)); x %= n;
생성 25 mar.
마크 애 머리 39k21170211
그러나 RAND_MAX
( RM
)가 N
(Where N
= 가능한 유효한 결과 수) 의 배수보다 1이 작은 시나리오에서 유효한 결과 집합 1 개를 버리는 경고 가 있습니다.
즉, '버려진 값 수'( D
) 가과 같으면 N
실제로 유효 집합 ( V)
)이 아니라 유효하지 않은 집합 ( I
)이 아닙니다 .
이것이 발생하는 것은 마크의 차이의 시력 상실 어떤 지점에 N
와 Rand_Max
.
N
유효한 멤버 수는 긍정적 인 정수로만 구성된 집합입니다. 여기에는 유효한 응답 수가 포함되어 있습니다. (예 : Set N
= {1, 2, 3, ... n }
)
Rand_max
그러나 (우리의 목적을 위해 정의 된) 음수가 아닌 정수를 포함하는 세트입니다.
가장 일반적인 형태로, 여기에 정의 된 것은 Rand Max
모든 유효한 결과 집합으로 이론적으로 음수 또는 숫자가 아닌 값을 포함 할 수 있습니다.
따라서 Rand_Max
"가능한 응답"세트로 정의하는 것이 좋습니다.
그러나 N
유효한 응답 세트 내의 값 수에 대해 작동하므로 특정 경우에 정의 된 경우 Rand_Max
에도 포함 된 총 수보다 하나 적은 값이됩니다.
Mark 's Solution을 사용하면 다음과 같은 경우 값이 삭제됩니다. X => RM-RM % N
EG:
Ran Max Value (RM) = 255
Valid Outcome (N) = 4
When X => 252, Discarded values for X are: 252, 253, 254, 255
So, if Random Value Selected (X) = {252, 253, 254, 255}
Number of discarded Values (I) = RM % N + 1 == N
IE:
I = RM % N + 1
I = 255 % 4 + 1
I = 3 + 1
I = 4
X => ( RM - RM % N )
255 => (255 - 255 % 4)
255 => (255 - 3)
255 => (252)
Discard Returns $True
위의 예에서 볼 수 있듯이 X 값 (초기 함수에서 얻은 난수)이 252, 253, 254 또는 255이면이 4 개의 값이 유효한 반환 값 세트를 구성하더라도이를 버립니다. .
IE : 버리기 (I) = N (유효한 결과 수) 값의 개수가 원래 함수에 의해 유효한 반환 값 세트를 버립니다.
값 N과 RM의 차이를 D로 설명하면 다음과 같습니다.
D = (RM - N)
그리고, D의 값이 작아 질수록,이 방법으로 인한 불필요한 재롤의 백분율은 자연 곱셈마다 증가합니다. (RAND_MAX가 소수와 같지 않을 경우 이는 유효한 문제입니다)
EG :
RM=255 , N=2 Then: D = 253, Lost percentage = 0.78125%
RM=255 , N=4 Then: D = 251, Lost percentage = 1.5625%
RM=255 , N=8 Then: D = 247, Lost percentage = 3.125%
RM=255 , N=16 Then: D = 239, Lost percentage = 6.25%
RM=255 , N=32 Then: D = 223, Lost percentage = 12.5%
RM=255 , N=64 Then: D = 191, Lost percentage = 25%
RM=255 , N= 128 Then D = 127, Lost percentage = 50%
필요한 Reroll의 비율이 N이 RM에 가까워 질수록 증가하기 때문에 코드를 실행하는 시스템의 제약 조건과 찾는 값에 따라 여러 가지 다른 값에서 유효한 문제가 될 수 있습니다.
이를 무효화하기 위해 다음과 같이 간단한 수정을 할 수 있습니다.
int x;
do {
x = rand();
} while (x > (RAND_MAX - ( ( ( RAND_MAX % n ) + 1 ) % n) );
x %= n;
이것은 계수를 사용하여 최대 값을 정의하는 추가 특성을 설명하는보다 일반적인 버전의 공식을 제공합니다.
N을 곱한 RAND_MAX에 작은 값을 사용하는 예
Mark'original Version :
RAND_MAX = 3, n = 2, Values in RAND_MAX = 0,1,2,3, Valid Sets = 0,1 and 2,3.
When X >= (RAND_MAX - ( RAND_MAX % n ) )
When X >= 2 the value will be discarded, even though the set is valid.
일반화 된 버전 1 :
RAND_MAX = 3, n = 2, Values in RAND_MAX = 0,1,2,3, Valid Sets = 0,1 and 2,3.
When X > (RAND_MAX - ( ( RAND_MAX % n ) + 1 ) % n )
When X > 3 the value would be discarded, but this is not a vlue in the set RAND_MAX so there will be no discard.
또한 N이 RAND_MAX의 값 수 여야하는 경우; 이 경우 RAND_MAX = INT_MAX가 아니라면 N = RAND_MAX +1을 설정할 수 있습니다.
루프 방식으로 N = 1을 사용할 수 있으며 X의 모든 값이 허용되지만 최종 승수에 IF 문을 넣습니다. 그러나 아마도 함수가 n = 1로 호출 될 때 1을 반환하는 유효한 이유가있을 수있는 코드가있을 수 있습니다 ...
따라서 n = RAND_MAX + 1을 원할 때 일반적으로 Div 0 오류를 제공하는 0을 사용하는 것이 좋습니다.
일반화 된 버전 2 :
int x;
if n != 0 {
do {
x = rand();
} while (x > (RAND_MAX - ( ( ( RAND_MAX % n ) + 1 ) % n) );
x %= n;
} else {
x = rand();
}
이 두 솔루션 모두 RM + 1이 n의 곱일 때 발생하는 불필요하게 폐기 된 유효한 결과로 문제를 해결합니다.
두 번째 버전은 또한 RAND_MAX에 포함 된 총 가능한 값 집합과 같도록 n이 필요한 경우의 시나리오를 다룹니다.
수정 된 접근 방식은 동일하며 유효한 난수를 제공하고 폐기 된 값을 최소화해야하는보다 일반적인 솔루션을 제공합니다.
반복해서 :
마크의 예를 확장하는 기본 일반 솔루션 :
// Assumes:
// RAND_MAX is a globally defined constant, returned from the environment.
// int n; // User input, or externally defined, number of valid choices.
int x;
do {
x = rand();
} while (x > (RAND_MAX - ( ( ( RAND_MAX % n ) + 1 ) % n) ) );
x %= n;
RAND_MAX + 1 = n의 한 가지 추가 시나리오를 허용하는 확장 일반 솔루션 :
// Assumes:
// RAND_MAX is a globally defined constant, returned from the environment.
// int n; // User input, or externally defined, number of valid choices.
int x;
if n != 0 {
do {
x = rand();
} while (x > (RAND_MAX - ( ( ( RAND_MAX % n ) + 1 ) % n) ) );
x %= n;
} else {
x = rand();
}
일부 조건 (특히 해석 된 언어)에서 while 조건 이외의 비교 연산 계산을 수행하면 재시도 횟수에 관계없이 일회성 계산이므로 더 빠른 결과를 얻을 수 있습니다. YMMV!
// Assumes:
// RAND_MAX is a globally defined constant, returned from the environment.
// int n; // User input, or externally defined, number of valid choices.
int x; // Resulting random number
int y; // One-time calculation of the compare value for x
if n != 0 {
y = RAND_MAX - ( ( ( RAND_MAX % n ) + 1 ) % n)
do {
x = rand();
} while (x > y);
x %= n;
} else {
x = rand();
}
RAND_MAX%n = n - 1
A의 RAND_MAX
값 3
은 편견이 있음이 계산에서 의미가 있습니다 (실제로는 그보다 훨씬 더 높아야한다하지만 편견은 여전히 존재하는 것) :
1 % 2 = 1
2 % 2 = 0
3 % 2 = 1
random_between(1, 3) % 2 = more likely a 1
이 경우 와 % 2
사이의 임의의 숫자를 원할 때 수행하지 말아야 할 것 입니다. 당신은 사이의 임의의 숫자를 얻을 수 및 수행하여 때문에이 경우에는, 비록 : 의 배수입니다 .0
1
0
2
% 3
RAND_MAX
3
다른 방법
훨씬 간단하지만 다른 답변에 추가하려면 다음 0
과 n - 1
같이 n
다른 가능성 사이에 임의의 숫자를 얻는 편향 방법이 있습니다.
>= n
이면 재시작하십시오 (모듈로 없음).실제로 임의의 데이터를 얻기가 쉽지 않으므로 필요한 것보다 많은 비트를 사용하는 이유는 무엇입니까?
다음은 의사 난수 생성기의 비트 캐시를 사용하는 Smalltalk의 예입니다. 나는 보안 전문가가 아니기 때문에 자신의 책임하에 사용하십시오.
next: n
| bitSize r from to |
n < 0 ifTrue: [^0 - (self next: 0 - n)].
n = 0 ifTrue: [^nil].
n = 1 ifTrue: [^0].
cache isNil ifTrue: [cache := OrderedCollection new].
cache size < (self randmax highBit) ifTrue: [
Security.DSSRandom default next asByteArray do: [ :byte |
(1 to: 8) do: [ :i | cache add: (byte bitAt: i)]
]
].
r := 0.
bitSize := n highBit.
to := cache size.
from := to - bitSize + 1.
(from to: to) do: [ :i |
r := r bitAt: i - from + 1 put: (cache at: i)
].
cache removeFrom: from to: to.
r >= n ifTrue: [^self next: n].
^r
허용되는 답변에서 알 수 있듯이 "모듈러스 바이어스"는 낮은 값의 근본을 갖습니다 RAND_MAX
. 그는 매우 작은 값 RAND_MAX
(10)을 사용하여 RAND_MAX가 10 인 경우 %를 사용하여 0과 2 사이의 숫자를 생성하려고 시도하면 다음 결과가 발생 함을 보여줍니다.
rand() % 3 // if RAND_MAX were only 10, gives
output of rand() | rand()%3
0 | 0
1 | 1
2 | 2
3 | 0
4 | 1
5 | 2
6 | 0
7 | 1
8 | 2
9 | 0
따라서 0의 4 개 출력 (4/10 확률)과 1과 2의 3 개의 출력 (각 3/10 기회)이 있습니다.
편향되어 있습니다. 숫자가 낮을수록 나올 확률이 높습니다.
그러나 그것은 아주 RAND_MAX
작은 때에 만 분명히 나타납니다 . 또는 더 구체적으로, 당신이 모딩하는 숫자가에 비해 클 때RAND_MAX
.
루핑 보다 훨씬 더 나은 해결책은 (비효율적으로 제안되어서는 안됨) 훨씬 더 큰 출력 범위를 가진 PRNG를 사용하는 것입니다. 메르 센 트위스터 알고리즘 4,294,967,295의 최대 출력을 갖는다. MersenneTwister::genrand_int32() % 10
모든 의도와 목적 을 위해 그렇게함으로써 균등하게 분배되고 모듈로 바이어스 효과는 사라질 것입니다.
MT::genrand_int32()%2
0 (50 + 2.3e-8) %와 시간의 1 (50-2.3e-8) %를 선택합니다. 카지노의 RGN을 구축하지 않는 한 (아마도 훨씬 더 넓은 범위의 RGN을 사용할 것임) 사용자는 2.3e-8 %의 추가 시간을 느끼지 못합니다. 여기서 중요하지 않은 숫자에 대해 이야기하고 있습니다.
RAND_MAX
값을 사용 하면 모듈러스 바이어스가 감소하지만 제거하지는 않습니다. 루핑 것입니다.
RAND_MAX
당신에 의해 모딩하는 수보다 충분히 더 큰, 횟수는 임의의 숫자가 하고서과 효율성에 영향을주지 않습니다 다시 생성해야합니다. 나는 당신이 받아 들인 대답에서 제안한 n
것보다 가장 큰 배수에 대해 테스트하는 한 반복문을 유지한다고 말합니다 n
.
방금 Von Neumann의 Unbiased Coin Flip Method에 대한 코드를 작성했습니다.이 코드는 이론적으로 난수 생성 프로세스의 바이어스를 제거해야합니다. 더 많은 정보는에서 찾을 수 있습니다 ( http://en.wikipedia.org/wiki/Fair_coin )
int unbiased_random_bit() {
int x1, x2, prev;
prev = 2;
x1 = rand() % 2;
x2 = rand() % 2;
for (;; x1 = rand() % 2, x2 = rand() % 2)
{
if (x1 ^ x2) // 01 -> 1, or 10 -> 0.
{
return x2;
}
else if (x1 & x2)
{
if (!prev) // 0011
return 1;
else
prev = 1; // 1111 -> continue, bias unresolved
}
else
{
if (prev == 1)// 1100
return 0;
else // 0000 -> continue, bias unresolved
prev = 0;
}
}
}
rand() % 100
100 번 호출 . B) 모든 결과가 다르면 첫 번째 결과를 취하십시오. C) 그렇지 않으면 GOTO A. 이것은 효과가 있지만 약 10 ^ 42의 예상 반복 횟수로 꽤 인내심을 가져야합니다. 그리고 불멸의.
else if(prev==2) prev= x1; else { if(prev!=x1) return prev; prev=2;}
RAND_MAX%n == n - 1
_입니다(RAND_MAX + 1) % n == 0
. 코드를 읽을 때 다른 코드% something == 0
를 계산하는 것보다 쉽게 "분할 할 수없는" 것으로 이해하는 경향이 있습니다. 물론, C ++ 다음 stdlib가있는 경우RAND_MAX
와 같은 값으로INT_MAX
,(RAND_MAX + 1)
확실하게 작동하지 않을 것입니다; 따라서 Mark의 계산은 가장 안전한 구현으로 유지됩니다.