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


108

これは以前に投稿された質問の続きです:

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

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

これを行うにはどうすればよいですか?


3
あなたが参照する質問の2番目の答えを見ると、答えがあります。rand()%6.
Mats Fredriksson

2
私はそれがどのように機能するのか理解していなかったので、明確にするために別の質問をすることにしました。
ジェイミーキーリング

2
ランダムな思考:プログラマーのランダムな断面をポーリングした場合、ランダムな数のプログラマーがランダムに数値を生成する方法をランダムに考えていることがわかります。宇宙が正確で予測可能な法則に支配されていることを考えると、物事をよりランダムに生成しようとするのは興味深いことではないでしょうか。このような質問は、常に10k以上のポスターを引き出す傾向があります。
Armstrongest

2
@Mats rand()%6は0を返す可能性があります。ダイスには適していません。
new123456

stackoverflow.com/a/6852396/419にリンクする回答ではなく、承認された回答としてマークを付けることはできますか。
Kev

回答:


173

これまでのすべての答えは数学的に間違っています。返す間隔rand() % Nの範囲の長さを2の累乗で除算し[0, N)ない限り、範囲内の数値Nrand()返されることはありません。さらに、の係数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が回答で示したリンクにも記載されています。(のmanページに記載されているように)より良いディストリビューションを持っているrandom()のでrand()、私は使用していますrand()

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


1
@Adam Rosenfield、@ Ryan Reich:Adamが回答した関連質問:stackoverflow.com/questions/137783/…最も賛成された回答: 'modulus'の使用法は正しくないでしょう?1..21から1..7を生成するには、ライアンが説明した手順を使用する必要があります。間違っている場合は修正してください。
Arvind 2013

1
さらに検討すると、ここでのもう1つの問題は、これがのときmax - min > RAND_MAXに機能しないことです。これは、前述の問題よりも深刻です(たとえば、VC ++にはRAND_MAX32767しかない)。
interjay 2013年

2
whileループを読みやすくすることができます。条件付きで代入を実行するのではなく、おそらくdo {} while()
theJPster 2014

4
こんにちは、この回答は、OSの彗星の本で引用されています;)
教科書

3
OSTEPの本でも引用されています:) pages.cs.wisc.edu/~remzi/OSTEP(第9章、4ページ)
rafascar

33

@Ryan Reichの回答に続き、私は私のクリーンアップしたバージョンを提供すると思いました。最初の境界チェックは2番目の境界チェックを前提として不要であり、再帰的ではなく反復的にしました。これは、範囲[MIN、MAX]の値を返すmax >= min1+max-min < RAND_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);
}

28
範囲> = RAND_MAXの場合、これは無限ループに陥ります。私にどのように知っているか聞いてください:/
theJPster 2013

24
どうして知っていますか!?
ファンタスティックMr Fox

1
intをunsigned int(r> = limit)と比較していることに注意してください。この問題は、< および<=なのでlimit、int(およびオプションでbucket)を作成することで簡単に解決できます。編集:提案を送信して編集しました。RAND_MAX / rangeINT_MAXbuckets * rangeRAND_MAX
rrrrrrrrrrrrrrrr 2017年

@Ryan Reichの解決策により、より良い(偏りの少ない)分布が得られます
ウラジミール

20

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

r = (rand() % (max + 1 - min)) + min

9
ライアンの回答で述べたように、これは偏った結果を生み出します。
David Wolever、2014

6
結果にバイアスがかかり、でintオーバーフローする可能性がありmax+1-minます。
chux-モニカを

1
これは整数の最小値と最大値でのみ機能します。最小値と最大値が浮動小数点数の場合、%演算を実行することはできません
Taioli Francesco

17
unsigned int
randr(unsigned int min, unsigned int max)
{
       double scaled = (double)rand()/RAND_MAX;

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

その他のオプションについては、こちらをご覧ください。


2
@ S.Lott-そうではない。わずかに高いオッズのケースをそれぞれ異なる方法で分配します、それだけです。二重の計算では、精度が高いという印象を与えますが、同じように簡単に使用でき、(((max-min+1)*rand())/RAND_MAX)+minおそらくまったく同じ分布を得ることができます(RAND_MAXがintに比べて十分小さいため、オーバーフローしないと仮定します)。
Steve314

4
これは少し危険です。、またはに非常に近いmax + 1場合rand() == RAND_MAX、またはrand()非常に近い場合にが返される可能性がRAND_MAXあり、浮動小数点エラーが最終結果を超えmax + 1ます。安全のために、結果を返す前に結果が範囲内であることを確認する必要があります。
Mark Dickinson

1
@Christoph:に同意しRAND_MAX + 1.0ます。max + 1ただし、これが戻りを防ぐのに十分かどうかはまだわかりません。特に、+ min最後max + 1にrand()の大きな値が生成される可能性のあるラウンドが含まれています。このアプローチを完全に破棄し、整数演算を使用する方が安全です。
Mark Dickinson、

3
クリストフが示唆RAND_MAXするRAND_MAX+1.0ようにに置き換えられた場合、+ min整数演算を使用して行われる限り、これは安全であると私は信じています return (unsigned int)((max - min + 1) * scaled) + min。(非自明な)理由は、IEEE 754算術演算を想定し、ラウンド半に-でも、(そしてまた、そのことでmax - min + 1、二重のように正確に表現され、それは典型的なマシン上で真のでしょう)、それは常に本当x * scaled < xのために任意の正のdouble xと任意のdouble scaledを満たす0.0 <= scaled && scaled < 1.0
Mark Dickinson

1
以下のための失敗randr(0, UINT_MAX):常に0を生成
chux -復活モニカ

12

あなたはただやってみませんか?

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

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


1
1〜6の結果が得られます。それが+ 1の目的です。
Armstrongest

4
サイモン、rand()ジェネレーターの状態の下位ビットが含まれる場所で使用されているlibcを見せてください(LCGを使用している場合)。私はこれまでこれを見たことはありません。それらのすべて(はい、RAND_MAXが32767であるMSVCを含む)は下位ビットを削除します。係数を使用することは、他の理由で推奨されません。つまり、係数が小さいほど、分布が歪むためです。
Joey

@ヨハネス:だから、スロットマシンはモジュラスを使わないと言っても安全ですか?
Armstrongest

0を除外するにはどうすればよいですか?30のループで実行すると、2回目または3回目に実行すると、およそ半分の途中に0があるようです。これは一種のまぐれですか?
ジェイミーキーリング

@ヨハネス:たぶん最近はそれほど問題ではないかもしれませんが、伝統的に下位ビットを使用することはお勧めできません。c-faq.com/lib/randrange.html
jamesdlin

9

バイアスの問題は理解しているが、拒否ベースのメソッドの予測できない実行時間に耐えられない人のために、このシリーズは、[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 + 1未満であるn(のように、この質問)、またはそれは2の累乗でない場合は、しかし場合は注意がオーバーフロー、整数避けるようにしなければならないRAND_MAX * n大きさです。


2
RAND_MAXであることが多いINT_MAXため、RAND_MAX + 1-> UB(INT_MINのように)
chux-モニカを2014年

@chuxは、「整数RAND_MAX * nが大きい場合は整数オーバーフローを回避するように注意する必要がある」という意味です。要件に適したタイプを使用するように調整する必要があります。
sh1 2014

@chux RAND_MAXINT_MAX「はい」であることが多いですが、16ビットシステムのみです。合理的に近代的なアーキテクチャはINT_MAX2 ^ 32/2およびRAND_MAX2 ^ 16/2に配置されます。これは誤った仮定ですか?

2
テストされた今日の2の32ビット@cat intコンパイラ、私が見つかりました。RAND_MAX == 327671にし、RAND_MAX == 2147483647他の上。私の全体的な経験(数十年)はそれ RAND_MAX == INT_MAXよりも頻繁です。そのため、合理的に現代的な32ビットアーキテクチャにRAND_MAXat があることには同意できません2^16 / 2。C仕様ではが許可さ32767 <= RAND_MAX <= INT_MAXれているので、傾向ではなくとにかくそれをコーディングします。
chux-モニカを2016年

3
「整数のオーバーフローを回避するように注意する必要があります」でまだカバーされています。
sh1 2016年

4

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

arc4random_uniform(MAX-MIN)+MIN

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

arc4random_uniform(20-10)+10

arc4random_uniform(10)+10

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


1
Woohoo、これは他の回答より10億倍優れています。あなたが#include <bsd/stdlib.h>最初にする必要があることに注意する価値があります。また、MinGWまたはCygWinなしでWindowsでこれを取得する方法はありますか?

1
いいえ、他の回答はより一般的であるため、それ自体は他の回答よりも優れていません。ここでは、arc4randomに限定されています。他の回答では、別のランダムソースを選択し、別の数値タイプで操作できます...そして最後に、誰かが問題を理解するのを助けるかもしれません。...それにもかかわらず、質問もいくつかの特別な要件やarc4randomへのアクセスなしがあるかもしれない他の人のための興味深いされていることを忘れてはいけない場合、あなたはそれへのアクセス権を持っているし、迅速な解決をしたい、それは😊確かに非常に良い答えです
K.ビアマン

4

以下は、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

1
RAND_MAX + 1簡単にオーバーフローできintます。その場合、(RAND_MAX + 1) % range疑わしい結果が生成されます。考えてみましょう(RAND_MAX + (uint32_t)1)
chuxを-復活モニカ

2

ライアンは正しいですが、解はランダム性の原因について知られていることに基づいてはるかに単純になる可能性があります。問題を再度述べるには:

  • 乱数の発生源があり、範囲内の整数[0, MAX)を均一な分布で出力します。
  • 目標は、の範囲[rmin, rmax]で一様に分布するランダムな整数を生成すること0 <= rmin < rmax < MAXです。

ビン(または「ボックス」)の数は、元の番号の範囲、より著しく小さい場合私の経験では、元のソース暗号的に強いです-すべてがrigamaroleを通過する必要がない、とシンプルなモジュロ除算だろう(output = rnd.next() % (rmax+1)ifのようにrmin == 0)十分であり、速度を失うことなく、「十分」に均一に分布する乱数を生成します。重要な要素はランダム性のソースです(つまり、子供たち、これを自宅で試してはいけませんrand())。

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

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.htmなどの第二次世界大戦の暗号マシン用の暗号的に強力なコードブックの生成など) )。出力には、かなりの偏りはありません。

暗号学的に強力な(真の)乱数ジェネレーターのソースは次のとおりです: インテルデジタル乱数ジェネレーター と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はこれらの新しい命令をサポートしていないため)。


でコンパイルされたgcc randu.c -o randu -Wa,q(Ubuntuの16上のGCC 5.3.1)またはclang randu.c -o randu(クラン3.8.0)動作しますが、使用して実行時にコアをダンプしますIllegal instruction (core dumped)。何か案は?
猫は

まず、CPUが実際にRDRAND命令をサポートしているかどうかはわかりません。お使いのOSはかなり最近ですが、CPUは最新ではない可能性があります。2番目(しかし、これはあまりありそうもありません)-Ubuntuにどのような種類のアセンブラが含まれているのかわかりません(Ubuntuは、パッケージを更新する場合、かなり後方に向かう傾向があります)。CPUがRDRANDをサポートしているかどうかをテストする方法については、私が参照したIntelサイトを確認してください。
マウス

あなたは確かに良い点を持っています。私がまだ手に入れることができないのは、何がそんなに悪いのかということですrand()。私はいくつかのテストを試してこの質問を投稿しましたが、決定的な答えはまだ見つかりません。
myradio

1

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

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]);
    }

}

rand()から数値を拒否すると、非常に非効率になります。これは、範囲が2 ^ k + 1として記述できるサイズである場合に特に非効率的です。遅いrand()呼び出しからのすべての試行のほぼ半分が条件によって拒否されます。RAND_MAXモジュロ範囲を計算する方が良いでしょう。のように:v = rand(); if (v > RAND_MAX - (RAND_MAX % range) -> reject and try again; else return v % range;モジュロはマスキングよりもはるかに遅い操作であることを理解していますが、それでもテストする必要があると思います。
–ØysteinSchønning-Johansen2013

rand()int範囲内のを返します[0..RAND_MAX]。その範囲は簡単にその範囲の一部になり、範囲内の値を生成することはuint32_tありrandomInRange(0, ,b)ません(INT_MAX...b]
chux-モニカを復活させる

0

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

#define rand01() (((double)random())/((double)(RAND_MAX)))
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.