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


157

特定の範囲(境界値を含む)でランダムな整数を生成する関数が必要です。不合理な品質/ラン​​ダム性の要件はありません。4つの要件があります。

  • 私はそれが速くなる必要があります。私のプロジェクトでは数百万(場合によっては数千万)の乱数を生成する必要があり、現在のジェネレーター関数がボトルネックであることが判明しています。
  • 私はそれが適度に均一である必要があります(rand()の使用は完全にうまくいきます)。
  • min-maxの範囲は、<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、1の比率は37:50:13になります。

より良い処方を提案していただけませんか?(または全体の疑似乱数ジェネレータ関数)



3
@ビル・マグリフ:はい。同じ問題があります。簡略化したバージョンは、次のとおりです。どのようにしてキャンディー10個を3人の子供に均等に(キャンディーを壊さずに)分割できますか?答えは、あなたがすることはできません-あなたはそれぞれの子供に3つを与える必要があり、誰にも10番目のものを与えないでください。
ジェリー棺

5
Boost.Randomを見たことがありますか?
Fred Nurk、2011

3
Andrew Koenigの記事「ほとんど決して正しく解決されない単純な問題」を確認してください。drdobbs.com
Gene Bushuyev

1
@ジーン・ブシュエフ:アンドリューと私はこの問題についてかなり長い間取り組んでいます。:参照groups.google.com/group/comp.lang.c++/browse_frm/thread/...を、そして:groups.google.com/group/comp.os.ms-windows.programmer.tools.mfc/...
ジェリーの棺

回答:


105

高速で、あなたのそれよりもいくらか優れていますが、それでも適切に均一な分散ソリューションではありません

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

範囲のサイズが2の累乗である場合を除いて、この方法では、の品質に関係なく、偏った不均一な分布数が生成されますrand()。このメソッドの品質の包括的なテストについては、こちらをお読みください。


2
おかげで、これは簡単なテストから私には十分なようです--1、0、1の分布はほぼ33:33:33です。
マテイZábský

3
常に最大値を返します。私はここで何かを逃していますか?:|
rohan-patel 2013

15
rand()C ++では有害であると見なす必要があります。均一に分散され、実際にはランダムなものを取得するには、より優れた方法があります。
Mgetz 2013

1
それは本当に100%の範囲内で正しい数を返しますか?再帰を使用して「正しい方法」で実行している他の
スタックオーバーフローの

2
多くの新しい読者にとって信頼できる情報源と思われる、非常に賛成(望ましい)回答なので、このソリューションの品質と潜在的な危険性について言及することは非常に重要だと思うので、編集しました。
プラズマセル

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 2015

8
私は「最高」ではなく、「最も単純な」(そして最も慣用的な)ことに同意します。残念ながら、標準の約保証付与するものではありませんrandom_device完全に壊れている可能性があり、いくつかの例を。さらに、mt19937は非常に優れた汎用的な選択肢ですが、高品質のジェネレーター(この比較を参照)の中で最速ではないため、OPの理想的な候補ではない可能性があります。
アルベルト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]に均一に分散された100万個のランダムs を生成するコードを次に示します。<chrono>あなたはパフォーマンスがあなたにとって大きな懸念であると述べたので、私はそれを時間を計るために新しいstd 機能を使いました。

#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.8 GHz Intel Core i5)の場合、これは次のように出力されます。

2.10268e + 07 1秒あたりの乱数。

コンストラクタに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

乱数ジェネレータとランダム分布を個別に制御することで、非常に自由になる場合があります。

また、(を使用してminstd_rand)この分布の最初の4つの「モーメント」を計算し(表示されていません)、それらを理論値と比較して、分布の質を定量化しようとしました。

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 2015年

問題は、分布の最小値と最大値が決して変化しないという事実によって、より簡単になります。d異なる境界を持つすべての反復で作成する必要がある場合はどうなりますか?ループの速度はどの程度低下しますか?
quant_dev 2018年

15

問題を2つの部分に分けましょう:

  • n0から(max-min)の範囲の乱数を生成します。
  • その数に分を加える

最初の部分は明らかに最も難しいです。rand()の戻り値が完全に均一であると仮定しましょう。モジュロを使用すると、最初の(RAND_MAX + 1) % (max-min+1)数値にバイアスが追加されます。私たちは魔法変えることができるのであればRAND_MAXRAND_MAX - (RAND_MAX + 1) % (max-min+1)、もはや偏りはないだろう。

アルゴリズムの実行時間に疑似非決定性を許可する場合は、この直感を使用できることがわかります。rand()が大きすぎる数を返すときはいつでも、十分に小さい数が得られるまで、別の乱数を要求するだけです。

実行している時間が今されて幾何学的に分散期待値と、最初の試行で十分に小さな数を得る確率です。以来、常に未満である、我々はそれを知っている反復の期待数が常に少ない任意の範囲のための2つ以上になりますので、。この手法を使用すると、標準のCPUで1秒未満で数千万の乱数を生成できるはずです。1/ppRAND_MAX - (RAND_MAX + 1) % (max-min+1)(RAND_MAX + 1) / 2p > 1/2

編集:

上記は技術的に正しいですが、DSimonの答えはおそらく実際にはより有用です。これは自分で実装しないでください。私は拒絶サンプリングの多くの実装を見てきましたが、それが正しいかどうかを確認することはしばしば非常に困難です。


完全を期すために、これはRejection Samplingです。
エタリオン

3
面白い事実:Joel Spolskyはかつてこの質問のバージョンを、StackOverflowがどのように答えたのかの一例として言及しました。私はその時の拒絶サンプリングを含むサイト上で答えて見て、すべての 単一 一つが間違っていました。
ヨルゲン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()

メルセンヌツイスターは、松本誠と西村拓司によって発明された「乱数」ジェネレータです。彼らのウェブサイトには、アルゴリズムの多数の実装が含まれています。

本質的に、メルセンヌツイスターは非常に大きな線形フィードバックシフトレジスタです。アルゴリズムは、32ビットの符号なし整数の624要素の配列に格納されている19,937ビットシードで動作します。値2 ^ 19937-1はメルセンヌ素数です。シードを操作する手法は、古い「ツイスト」アルゴリズムに基づいているため、「メルセンヌツイスター」という名前です。

Mersenne Twisterの魅力的な側面は、数値を生成するために、時間のかかる乗算ではなく、二項演算を使用することです。このアルゴリズムには、非常に長い期間と細分性があります。それは非暗号化アプリケーションに対して高速で効果的です。


1
Mersenneツイスターは優れたジェネレーターですが、基になるジェネレーター自体に関係なく、彼が扱っている問題は残っています。
ジェリー棺

(私のプロジェクトはライブラリーであるため)プロジェクトに別の依存関係を導入することを意味するので、ランダムジェネレーターのためだけにBoostを使用したくありません。将来はとにかく使用せざるを得なくなるので、このジェネレーターに切り替えることができます。
マテイZábský

1
@ジェリー棺どの問題?それが彼のすべての要件を満たしたため、私はそれを提供しました:高速で、(boost::uniform_int分布を使用して)均一で、最小最大範囲を好きなように変換でき、シード可能です。
Aphex 2011

@mzabskyプロジェクトを教授に提出して提出する必要があったとき、私はおそらくそれを止めさせません。使用していた関連するブーストヘッダーファイルを含めただけです。40mbブーストライブラリ全体をコードと一緒にパッケージ化する必要はありません。もちろん、あなたの場合、これは著作権などの他の理由で実現できないかもしれません...
Aphex

@Aphex 私のプロジェクトは実際には科学的なシミュレーターではなく、本当に均一な分布を必要とするものではありません。私は古いジェネレーターを1.5年間問題なく使用しました。非常に狭い範囲(この場合は3)から数値を生成するために最初に必要なときに偏った分布に気づきました。ただし、速度はブーストソリューションを検討するための引数です。ライセンスを調べて、必要な数個のファイルをプロジェクトに追加できるかどうかを確認します。今のところ「チェックアウト-> F5->使用する準備ができている」のが好きです。
マテイ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()の結果を無視してください。


その不人気にもかかわらず、これは私が非科学プロジェクトに使用するものでもあります。理解しやすく(数学の学位は必要ありません)、十分に機能します(これを使用してコードをプロファイルする必要はありませんでした)。:)範囲が大きい場合は、2つのrand()値を一緒に使用して、30ビットの値を処理することができます(RAND_MAX = 0x7fff、つまり15のランダムビットを想定)
efotinis

整数オーバーフロー警告を回避するように変更RAND_MAX(double) RAND_MAXます。
アレックス

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、

@ジェリー:新しいバージョンを確認してください。
エレミヤウィルコック

私はそれが正しく機能することについて少し不確かです。それは可能かもしれませんが、少なくとも私には正しさが明らかではないようです。
ジェリーコフィン

@ジェリー:ここに私の推論があります:範囲は[0, h)単純化のためであると仮定します。呼び出しにrand()RAND_MAX + 1可能な戻り値があります。それらのrand() % h折りたたみ(RAND_MAX + 1) / hを各h出力値に取ります。ただし(RAND_MAX + 1) / h + 1、それらの値が(RAND_MAX + 1) % hh出力を通る最後の部分サイクルのため)より小さい値にマップされている場合を除きます。したがって(RAND_MAX + 1) % h、偏りのない分布を得るために可能な出力を削除します。
エレミヤウィルコック


1

最小値と最大値がint値であると想定し、[および]はこの値を含めることを意味し、(および)はこの値を含めないことを意味し、上記を使用してc ++ rand()を使用して正しい値を取得します

参照:()[]の定義、参照:

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

randおよびsrand関数またはRAND_MAXの定義については、次のURLにアクセスしてください。

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

このスレッドでは、拒否のサンプリングについてはすでに説明しましたが、次の事実に基づいて1つの最適化を提案したいと思いました。 rand() % 2^somethingすでに述べたようにバイアスを導入しない。

アルゴリズムは本当にシンプルです:

  • 区間の長さより大きい2の最小べき乗を計算します
  • その「新しい」間隔で1つの数値をランダム化する
  • 元の間隔の長さより短い場合、その数を返します
    • そうでなければ拒否する

これが私のサンプルコードです:

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の累乗が実際の間隔の長さに「近づく」ため、特に短い間隔でうまく機能し、ミスの数が少なくなります。

PS
再帰を回避する方が明らかに効率的です(ログの上限を何度も計算する必要はありません)。しかし、この例の方が読みやすいと思いました。


0

ほとんどの提案では、rand()関数から取得した最初のランダムな値(通常は0からRAND_MAXまで)が単純に無駄になっていることに注意してください。あなたはそれから一つだけの乱数を作成していますが、あなたにもっと与えることができる健全な手順があります。

整数の乱数の[min、max]領域が必要だと仮定します。[0、max-min]から始めます

ベースb = max-min + 1を取る

基数bでrand()から取得した数値を表すことから始めます。

このようにして、floor(log(b、RAND_MAX))が得られます。底bの各桁は、最後の桁を除いて、[0、max-min]の範囲の乱数を表すためです。

もちろん、[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を提供する1つの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 ++のrandを使用していた。このスレッドで示されているように、[0、RAND_MAX]から[MIN、MAX]へのランダムな整数のマッピングは、統計的特性やパフォーマンスの破壊を避けたい場合、完全に簡単ではありません。範囲[0、1]にdoubleがある場合、マッピングは簡単です。
マテイZábský

2
答えは間違っています。代わりに係数を使用してください:int num = (int) rand() % (max - min) + min;
Jaime Ivan Cervantes '28

-2

次の表現は、私が誤っていない限り公平である必要があります。

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

ここでは、rand()が、1.0を含まない0.0から1.0の範囲のランダムな値を与え、maxとminがmin <maxの条件の整数であると仮定しています。


std::floorを返しますdouble。ここでは整数値が必要です。をint使用する代わりに、単にキャストしstd::floorます。
musiphil 2013
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.