web-dev-qa-db-ja.com

範囲内からランダムな整数を生成する方法

これは以前に投稿された質問のフォローです:

Cで乱数を生成する方法?

ダイの側面を模倣するために、1から6などの特定の範囲内から乱数を生成できるようにしたいと考えています。

これをどうやってやるの?

100
Jamie Keeling

これまでのすべての答えは数学的に間違っています。 Rand() % Nを返すことは、NRand()が返す間隔の長さを分割しない限り、[0, N)の範囲の数値を一様に与えません(つまり、2のべき乗です)。さらに、Rand()のモジュラスが独立であるかどうかはわかりません。それらは0, 1, 2, ...になる可能性があります。合理的であると思われる唯一の仮定は、Rand()がポアソン分布を出すことです。同じサイズの2つの非重複部分区間は、同様に可能性が高く独立しています。値の有限セットの場合、これは均一な分布を意味し、Rand()の値が適切に分散されることも保証します。

つまり、Rand()の範囲を変更する唯一の正しい方法は、それをボックスに分割することです。たとえば、Rand_MAX == 11で、1..6の範囲が必要な場合は、{0,1}を1に、{2,3}を2などに割り当てる必要があります。これらは、ばらばらで等しいサイズの間隔であるため、均一かつ独立して分散されます。

浮動小数点除算を使用するという提案は数学的にもっともらしいですが、原則として丸めの問題があります。おそらくdoubleは、動作させるのに十分な精度です。おそらくない。私にはわかりませんし、理解する必要もありません。いずれにしても、答えはシステムに依存します。

正しい方法は、整数演算を使用することです。つまり、次のようなものが必要です。

#include <stdlib.h> // For random(), Rand_MAX

// Assumes 0 <= max <= Rand_MAX
// Returns in the closed interval [0, max]
long random_at_most(long max) {
  unsigned long
    // max <= Rand_MAX < ULONG_MAX, so this is okay.
    num_bins = (unsigned long) max + 1,
    num_Rand = (unsigned long) Rand_MAX + 1,
    bin_size = num_Rand / num_bins,
    defect   = num_Rand % num_bins;

  long x;
  do {
   x = random();
  }
  // This is carefully written not to overflow
  while (num_Rand - defect <= (unsigned long)x);

  // Truncated division is intentional
  return x/bin_size;
}

ループは、完全に均一な分布を得るために必要です。たとえば、0から2までの乱数が与えられ、0から1までの乱数だけが必要な場合は、2が得られなくなるまで引き続けます。これが等しい確率で0または1を与えることを確認するのは難しくありません。この方法は、nosが回答で示したリンクでも説明されていますが、コーディング方法は異なります。 (random()のmanページに記載されているように)分布がより良いので、Rand()ではなくRand()を使用しています。

デフォルトの範囲[0, Rand_MAX]以外のランダムな値を取得したい場合は、何か注意が必要です。おそらく最も便利なのは、nビットをプルし(random_extended()を使用)、[0, 2**n)に戻る関数random_at_most()を定義し、random_at_most()の代わりにrandom_extended()random()を適用し(2**n - 1の代わりにRand_MAXを使用して、2**nよりも少ないそのような値を保持できる数値型があると仮定します。最後に、もちろん、負の値を含むmin + random_at_most(max - min)を使用して[min, max]の値を取得できます。

161
Ryan Reich

@Ryan Reichの回答に続いて、クリーンアップしたバージョンを提供すると思いました。 2番目の境界チェックを考慮すると、最初の境界チェックは不要であり、再帰的ではなく反復的に行いました。 max >= minおよび1+max-min < Rand_MAXの範囲[min、max]の値を返します。

unsigned int Rand_interval(unsigned int min, unsigned int max)
{
    int r;
    const unsigned int range = 1 + max - min;
    const unsigned int buckets = Rand_MAX / range;
    const unsigned int limit = buckets * range;

    /* Create equal size buckets all in a row, then fire randomly towards
     * the buckets until you land in one of them. All buckets are equally
     * likely. If you land off the end of the line of buckets, try again. */
    do
    {
        r = Rand();
    } while (r >= limit);

    return min + (r / buckets);
}
33
theJPster

範囲の最大値と最小値がわかっていて、その範囲内の数値を生成する場合の式は次のとおりです。

r = (Rand() % (max + 1 - min)) + min
19
Sattar
unsigned int
randr(unsigned int min, unsigned int max)
{
       double scaled = (double)Rand()/Rand_MAX;

       return (max - min +1)*scaled + min;
}

他のオプションについては here をご覧ください。

17
nos

あなたはそうしませんか:

srand(time(NULL));
int r = ( Rand() % 6 ) + 1;

%はモジュラス演算子です。基本的には、6で除算し、残りを0〜5で返します。

11
Armstrongest

バイアスの問題を理解しているが、拒否ベースのメソッドの予測不可能なランタイムに耐えられない人のために、このシリーズは[0, n-1]間隔で次第に偏りの少ないランダムな整数を生成します。

r = n / 2;
r = (Rand() * n + r) / (Rand_MAX + 1);
r = (Rand() * n + r) / (Rand_MAX + 1);
r = (Rand() * n + r) / (Rand_MAX + 1);
...

これは、i * log_2(Rand_MAX + 1)ビットの高精度の固定小数点乱数(iは反復回数)を合成し、nによる長い乗算を実行することにより行います。

ビット数がnに比べて十分に大きい場合、バイアスは非常に小さくなります。

Rand_MAX + 1nより小さい場合( この質問 のように)、または2のべき乗でない場合は問題になりませんが、Rand_MAX * nが大きい場合は整数オーバーフローを避けるように注意する必要があります。

8
sh1

モジュロバイアス(他の回答で提案)を回避するために、常に使用できます。

arc4random_uniform(MAX-MIN)+MIN

「MAX」は上限で、「MIN」は下限です。たとえば、10〜20の数値の場合:

arc4random_uniform(20-10)+10

arc4random_uniform(10)+10

シンプルなソリューションであり、「Rand()%N」を使用するよりも優れています。

4
magamig

以下は、Ryan Reichのソリューションよりもわずかに単純なアルゴリズムです。

/// Begin and end are *inclusive*; => [begin, end]
uint32_t getRandInterval(uint32_t begin, uint32_t end) {
    uint32_t range = (end - begin) + 1;
    uint32_t limit = ((uint64_t)Rand_MAX + 1) - (((uint64_t)Rand_MAX + 1) % range);

    /* Imagine range-sized buckets all in a row, then fire randomly towards
     * the buckets until you land in one of them. All buckets are equally
     * likely. If you land off the end of the line of buckets, try again. */
    uint32_t randVal = Rand();
    while (randVal >= limit) randVal = Rand();

    /// Return the position you hit in the bucket + begin as random number
    return (randVal % range) + begin;
}

Example (Rand_MAX := 16, begin := 2, end := 7)
    => range := 6  (1 + end - begin)
    => limit := 12 (Rand_MAX + 1) - ((Rand_MAX + 1) % range)

The limit is always a multiple of the range,
so we can split it into range-sized buckets:
    Possible-Rand-output: 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16
    Buckets:             [0, 1, 2, 3, 4, 5][0, 1, 2, 3, 4, 5][X, X, X, X, X]
    Buckets + begin:     [2, 3, 4, 5, 6, 7][2, 3, 4, 5, 6, 7][X, X, X, X, X]

1st call to Rand() => 13
    → 13 is not in the bucket-range anymore (>= limit), while-condition is true
        → retry...
2nd call to Rand() => 7
    → 7 is in the bucket-range (< limit), while-condition is false
        → Get the corresponding bucket-value 1 (randVal % range) and add begin
    => 3
4
K. Biermann

ライアンは正解ですが、ランダム性の原因について知られていることに基づいて、ソリューションははるかに簡単になります。問題を再度述べるには:

  • 一様分布の範囲[0, MAX)の整数を出力するランダム性のソースがあります。
  • 目標は、範囲[rmin, rmax]で均一に分散された乱数整数を生成することです(ここで0 <= rmin < rmax < MAX)。

私の経験では、ビン(または「ボックス」)の数が元の数の範囲よりもかなり小さい場合、and元のソースは暗号化されています強い-そのすべてのリガマロールを通過する必要はなく、単純なモジュロ除算で十分(rmin == 0の場合はoutput = rnd.next() % (rmax+1)のように)で、速度を損なうことなく均一に「十分に」分散される乱数を生成します鍵となる要因は、ランダム性のソースです(つまり、子供たちは、Rand()を使って自宅でこれを試さないでください)。

これが実際にどのように機能するかの例/証明です。 (Intel RDRANDに基づいて)ランダムバイトを生成する強力な暗号化ソースを持つ1〜22の乱数を生成したかった。結果は次のとおりです。

Rnd distribution test (22 boxes, numbers of entries in each box):     
 1: 409443    4.55%
 2: 408736    4.54%
 3: 408557    4.54%
 4: 409125    4.55%
 5: 408812    4.54%
 6: 409418    4.55%
 7: 408365    4.54%
 8: 407992    4.53%
 9: 409262    4.55%
10: 408112    4.53%
11: 409995    4.56%
12: 409810    4.55%
13: 409638    4.55%
14: 408905    4.54%
15: 408484    4.54%
16: 408211    4.54%
17: 409773    4.55%
18: 409597    4.55%
19: 409727    4.55%
20: 409062    4.55%
21: 409634    4.55%
22: 409342    4.55%   
total: 100.00%

これは、私の目的に必要な均一性に近いものです(公正なサイコロ投げ、 http://users.telenet.be/d.rijmenants/en/kl-7simなどのWWII暗号マシン用の暗号的に強力なコードブックを生成します.htm など)。出力には、かなりのバイアスは示されていません。

暗号的に強力な(真の)乱数ジェネレーターのソースは次のとおりです。 Intel Digital Random Number Generator および64ビット(符号なし)乱数を生成するサンプルコード。

int rdrand64_step(unsigned long long int *therand)
{
  unsigned long long int foo;
  int cf_error_status;

  asm("rdrand %%rax; \
        mov $1,%%edx; \
        cmovae %%rax,%%rdx; \
        mov %%edx,%1; \
        mov %%rax, %0;":"=r"(foo),"=r"(cf_error_status)::"%rax","%rdx");
        *therand = foo;
  return cf_error_status;
}

Mac OS Xでclang-6.0.1(ストレート)を使用してコンパイルし、gcc-4.8.3で「-Wa、q」フラグを使用しました(GASはこれらの新しい命令をサポートしていないため)。

2
Mouse

前に述べたように、モジュロは分布を歪めるので十分ではありません。ビットをマスクし、それらを使用して分布が歪んでいないことを確認するコードを以下に示します。

static uint32_t randomInRange(uint32_t a,uint32_t b) {
    uint32_t v;
    uint32_t range;
    uint32_t upper;
    uint32_t lower;
    uint32_t mask;

    if(a == b) {
        return a;
    }

    if(a > b) {
        upper = a;
        lower = b;
    } else {
        upper = b;
        lower = a; 
    }

    range = upper - lower;

    mask = 0;
    //XXX calculate range with log and mask? nah, too lazy :).
    while(1) {
        if(mask >= range) {
            break;
        }
        mask = (mask << 1) | 1;
    }


    while(1) {
        v = Rand() & mask;
        if(v <= range) {
            return lower + v;
        }
    }

}

次の簡単なコードを使用すると、分布を確認できます。

int main() {

    unsigned long long int i;


    unsigned int n = 10;
    unsigned int numbers[n];


    for (i = 0; i < n; i++) {
        numbers[i] = 0;
    }

    for (i = 0 ; i < 10000000 ; i++){
        uint32_t Rand = random_in_range(0,n - 1);
        if(Rand >= n){
            printf("bug: Rand out of range %u\n",(unsigned int)Rand);
            return 1;
        }
        numbers[Rand] += 1;
    }

    for(i = 0; i < n; i++) {
        printf("%u: %u\n",i,numbers[i]);
    }

}
1
Andrew Chambers

範囲[0,1]の浮動小数点数を返します。

#define Rand01() (((double)random())/((double)(Rand_MAX)))
0
Geremia