난수 생성기를 사용할 때 사람들이 모듈로 바이어스가 있다고 말하는 이유는 무엇입니까? rand()에 C

나는이 질문에 많은 질문을 보았지만 그것에 대한 확실한 대답은 보지 못했습니다. 그래서 여기 rand()에 C ++에서 와 같이 난수 생성기를 사용할 때 왜 “모듈러스 편향”이 있는지 이해하는 데 도움이 될 것 입니다.



답변

그래서 rand()0 사이의 자연수를 선택하고, 의사 난수 생성기 RAND_MAX에 정의 된 상수 인 cstdlib(이 참조 문서 에 대한 일반적인 개요가 rand()).

이제 0과 2 사이의 난수를 생성하려면 어떻게됩니까? 설명을 위해 RAND_MAX10 이라고 가정 하고을 호출하여 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 회 반복해야합니다.


답변

문제에 대해서는 @ user1413793이 맞습니다. 한 가지 점을 제외하고는 더 이상 논의하지 않을 것입니다. 예, 작은 값 n과 큰 값의 RAND_MAX경우 모듈러스 바이어스가 매우 작을 수 있습니다. 그러나 바이어스 유도 패턴을 사용한다는 것은 임의의 숫자를 계산할 때마다 바이어스를 고려해야하고 경우에 따라 다른 패턴을 선택해야한다는 것을 의미합니다. 그리고 만약 당신이 잘못된 선택을한다면, 그것이 도입 한 버그는 미묘하고 단위 테스트가 거의 불가능합니다. 적절한 도구 (예 :)를 사용하는 것과 비교할 때 arc4random_uniform, 이는 더 적은 작업이 아니라 추가 작업입니다. 더 많은 작업을하고 더 나쁜 솔루션을 얻는 것은 끔찍한 엔지니어링입니다. 특히 대부분의 플랫폼에서 매번 작업을 수행 할 때 특히 그렇습니다.

불행히도, 솔루션의 구현은 모두 잘못되었거나 덜 효율적입니다. (각 솔루션에는 문제를 설명하는 다양한 의견이 있지만 문제를 해결하기 위해 해결 된 솔루션은 없습니다.) 이는 일반적인 답을 찾는 사람을 혼란스럽게 만들 가능성이 높으므로 여기서 잘 알려진 구현을 제공하고 있습니다.

다시 말하지만 최상의 솔루션은 arc4random_uniform해당 플랫폼을 제공하는 플랫폼 또는 플랫폼과 유사한 범위의 솔루션 (예 : Random.nextIntJava)에서 사용하는 것입니다. 코드 비용없이 올바른 작업을 수행합니다. 이것은 거의 항상 올바른 전화입니다.

이없는 경우 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;
 }

답변

정의

모듈로 바이어스 는 모듈로 산술을 사용하여 출력 세트를 입력 세트의 서브 세트로 줄이는 고유의 바이어스입니다. 일반적으로, 출력 세트의 크기가 입력 세트의 크기의 제수가 아닌 경우 모듈로 산술을 사용하는 경우와 같이 입력과 출력 세트 사이의 매핑이 균등하게 분포되지 않을 때마다 바이어스가 존재합니다.

이 바이어스는 숫자가 비트 열 (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으로 줄일 수 있습니다. 그러나 이것은 모듈로 바이어스 문제를 나타냅니다 : 1100을 111산출하고 1을 산출합니다 .

잠재적 인 솔루션

접근법 0 :

이론 상으로는 임의의 비트에 의존하는 대신 작은 군대를 고용하여 하루 종일 주사위를 굴려 결과를 데이터베이스에 기록한 다음 각 결과를 한 번만 사용할 수 있습니다. 이것은 소리만큼이나 실용적이며, 어쨌든 진정한 임의의 결과를 얻지 못할 것입니다.

접근법 1 :

대신 계수를 사용하는 순진하지만 수학적으로 올바른 해결책은 폐기 결과 그 수율입니다 110그리고 111단순히 3 개의 새 비트 다시 시도하십시오. 불행히도, 이것은 각 롤마다 재롤을 포함하여 재롤이 필요할 확률25 % 라는 것을 의미합니다 . 이것은 가장 사소한 용도를 제외하고는 모두 비현실적입니다.

접근법 2 :

더 많은 비트를 사용하십시오. 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 %입니다.

이것은 두 가지 추가 질문을 제기합니다.

  1. 비트를 충분히 추가하면 폐기 확률이 줄어들 것이라는 보장이 있습니까?
  2. 일반적인 경우 몇 비트로 충분 합니까?

일반적인 해결책

고맙게도 첫 번째 질문에 대한 대답은 ‘예’입니다. 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;
}

대부분의 조건에서 실제로 얼마나 많은 재롤이 발생하는지 확인 하려면 MODULUSROLLS값을 사용하는 것이 좋습니다 . 회의론자는 계산 된 값을 파일에 저장하고 분포가 정상적으로 나타나는지 확인할 수 있습니다.


답변

모듈로 사용에 대한 두 가지 일반적인 불만이 있습니다.

  • 하나는 모든 발전기에 유효합니다. 제한적인 경우 더보기 쉽습니다. 제너레이터에 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)이 아닙니다 .

이것이 발생하는 것은 마크의 차이의 시력 상실 어떤 지점에 NRand_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();
}

답변

A의 RAND_MAX3은 편견이 있음이 계산에서 의미가 있습니다 (실제로는 그보다 훨씬 더 높아야한다하지만 편견은 여전히 존재하는 것) :

1 % 2 = 1
2 % 2 = 0
3 % 2 = 1
random_between(1, 3) % 2 = more likely a 1

이 경우 와 % 2사이의 임의의 숫자를 원할 때 수행하지 말아야 할 것 입니다. 당신은 사이의 임의의 숫자를 얻을 수 및 수행하여 때문에이 경우에는, 비록 : 의 배수입니다 .0102% 3RAND_MAX3

다른 방법

훨씬 간단하지만 다른 답변에 추가하려면 다음 0n - 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