216

サイコロを使ってゲームを作ろうとしていますが、その中に乱数を入れる必要があります (サイコロの面をシミュレートするためです。1 から 6 の間で作成する方法を知っています)。使用する

#include <cstdlib> 
#include <ctime> 
#include <iostream>

using namespace std;

int main() 
{ 
    srand((unsigned)time(0)); 
    int i;
    i = (rand()%6)+1; 
    cout << i << "\n"; 
}

プログラムを数回実行すると、次のような出力が得られるため、うまく機能しません。

6
1
1
1
1
1
2
2
2
2
5
2

したがって、同じ乱数を 5 回連続して生成するのではなく、毎回異なる乱数を生成するコマンドが必要です。これを行うコマンドはありますか?

4

12 に答える 12

271

モジュロを使用すると、乱数ジェネレーターによっては、乱数に偏りが生じる場合があります。詳細については、この質問を参照してください。もちろん、ランダムなシーケンスで繰り返し数を取得することは完全に可能です。

配布を改善するために、いくつかの C++11 機能を試してください。

#include <random>
#include <iostream>

int main()
{
    std::random_device dev;
    std::mt19937 rng(dev());
    std::uniform_int_distribution<std::mt19937::result_type> dist6(1,6); // distribution in range [1, 6]

    std::cout << dist6(rng) << std::endl;
}

C++11 乱数の詳細については、この質問/回答を参照してください。上記はこれを行う唯一の方法ではありませんが、1 つの方法です。

于 2012-11-18T23:33:50.383 に答える
130

テスト アプリケーションの最も基本的な問題は、 1 回呼び出してから 1 回srand呼び出しrandて終了することです。

関数の全体的なポイントは、疑似乱数のシーケンスをランダムシードでsrand初期化することです。

これは、2 つの異なるアプリケーション (同じ/実装)で同じ値をに渡すと、その後両方のアプリケーションでまったく同じ値のシーケンスが読み取られることを意味します。srandsrandrandrand()

ただし、サンプル アプリケーションでは、擬似ランダム シーケンスは 1 つの要素のみで構成されます。シードから生成された擬似ランダム シーケンスの最初の要素は、現在の1 sec精度の時間に等しくなります。その場合、出力に何が表示されると思いますか?

明らかに、同じ秒にアプリケーションを実行した場合-同じシード値を使用するため、結果はもちろん同じです(Martin Yorkが質問へのコメントですでに述べたように)。

実際には、srand(seed)一度呼び出しrand() てから何度も呼び出して、そのシーケンスを分析する必要があります。ランダムに見えるはずです。

修正 1 - コード例:

わかったよ。どうやら口頭での説明では不十分なようです (言語の壁か何か... :) )。

srand()/rand()/time()質問で使用されたのと同じ関数に基づく昔ながらの C コードの例:

#include <stdlib.h>
#include <time.h>
#include <stdio.h>

int main(void)
{
    unsigned long j;
    srand( (unsigned)time(NULL) );

    for( j = 0; j < 100500; ++j )
    {
        int n;

        /* skip rand() readings that would make n%6 non-uniformly distributed
          (assuming rand() itself is uniformly distributed from 0 to RAND_MAX) */
        while( ( n = rand() ) > RAND_MAX - (RAND_MAX-5)%6 )
        { /* bad value retrieved so get next one */ }

        printf( "%d,\t%d\n", n, n % 6 + 1 );
    }

    return 0;
}

^^^プログラムの 1 回の実行からのそのシーケンスは、ランダムに見えるはずです

以下で説明する理由により、本番環境で / 関数を使用することはrand勧めしません。また、IMO がすでに非常に明白であるという理由により、関数をランダム シードとして使用することも絶対にお勧めしません。これらは、教育目的やポイントを説明するためには問題ありませんが、深刻な用途にはほとんど役に立ちません。srandtime

修正 2 - 詳細な説明:

現在のところ、実際にランダムなデータを確実に生成する (つまり、実際にランダムであることが標準によって保証されている) C または C++ の標準機能 (ライブラリ関数またはクラス)は存在しないことを理解することが重要です。この問題に対処する唯一の標準機能はstd::random_deviceであり、残念ながら実際のランダム性を保証するものではありません。

アプリケーションの性質に応じて、真にランダムな (予測不可能な) データが本当に必要かどうかを最初に判断する必要があります。真のランダム性が最も確実に必要な注目すべきケースは、情報セキュリティです。たとえば、対称キー、非対称秘密キー、ソルト値、セキュリティ トークンなどを生成します。

ただし、セキュリティ グレードの乱数は別の記事に値する別の業界です。この私の回答でそれらについて簡単に説明しています。

ほとんどの場合、疑似乱数ジェネレーターで十分です (科学シミュレーションやゲームなど)。場合によっては、一貫して定義された疑似乱数シーケンスが必要になることさえあります。たとえば、ゲームでは、ディストリビューションに大量のデータを保存することを避けるために、実行時にまったく同じマップを生成することを選択できます。

元の質問と繰り返される多数の同一/類似の質問 (およびそれらに対する多くの誤った「回答」でさえ) は、何よりもまず、乱数と疑似乱数を区別し、疑似乱数シーケンスとは何かを理解することが重要であることを示しています。そもそも疑似乱数ジェネレーターは、真の乱数ジェネレーターを使用できるのと同じ方法で使用されないことを認識してください。

直感的に乱数を要求するとき - 返される結果は、以前に返された値に依存してはならず、誰かが以前に何かを要求したかどうかに依存してはならず、どの瞬間に、どのプロセスによって、どのコンピューターに、どのジェネレーターから、どのようなものに依存してはなりません。要求された銀河。それが「ランダム」という言葉の意味です - 予測不可能で、何からも独立している - そうでなければ、それはもはやランダムではありませんよね? この直感を使えば、Web で魔法の呪文を検索して、あらゆる可能なコンテキストでそのような乱数を取得するのはごく自然なことです。

^^^この種の直感的な期待は、疑似乱数ジェネレーターが関係するすべての場合において非常に間違っており、有害です- 真の乱数に対しては妥当ですが。

「乱数」という意味のある概念は (一種の) 存在しますが、「疑似乱数」のようなものはありません。疑似乱数ジェネレーターは、実際に疑似乱数シーケンスを生成します。

実際、疑似乱数シーケンスは常に決定論的です (そのアルゴリズムと初期パラメータによって事前に決定されます)。つまり、実際にはランダムなものは何もありません。

専門家が PRNG の品質について話すとき、実際には、生成されたシーケンス (およびその注目すべきサブシーケンス) の統計的特性について話します。たとえば、2 つの高品質の PRNG を順番に使用して組み合わせると、それぞれが適切なシーケンスを個別に生成しているにもかかわらず、悪い結果のシーケンスが生成される可能性があります (これらの 2 つの適切なシーケンスは単に互いに相関するため、うまく結合されない可能性があります)。

具体的にはrand()/srand(s)ペアの関数は、実装定義のアルゴリズムで生成された単一のプロセスごとの非スレッドセーフ (!) 疑似乱数シーケンスを提供します。関数rand()は range の値を生成します[0, RAND_MAX]

C11 規格 (ISO/IEC 9899:2011) からの引用:

このsrand関数は、引数を、後続の への呼び出しによって返される疑似乱数の新しいシーケンスのシードとして使用しますrand。が同じシード値で呼び出された場合 srand、疑似乱数のシーケンスが繰り返されます。randの呼び出しが行われる前に が呼び出された場合、シード値 1 で が最初に呼び出さsrandれたときと同じシーケンスが生成されます。srand

多くの人は、 から までの範囲の半独立な一様分布数のシーケンスが生成されると合理的に期待していrand()ます。確かにそうすべきです(そうでなければ役に立たない)が、残念ながら標準がそれを必要としないだけでなく、「生成されたランダムシーケンスの品質に関する保証はありません」という明示的な免責事項さえあります。いくつかの歴史的なケースでは、実装は実際に非常に質の悪いものでした. 最新の実装ではおそらく十分ですが、信頼は壊れており、回復するのは簡単ではありません. その上、非スレッドセーフな性質により、マルチスレッド アプリケーションでの安全な使用はトリッキーで制限されます (まだ可能です - 1 つの専用スレッドから使​​用することもできます)。0RAND_MAXrandsrand

新しいクラス テンプレートstd::mersenne_twister_engine<> (およびその便利な typedef - std::mt19937/std::mt19937_64適切なテンプレート パラメーターの組み合わせ) は、C++11 標準で定義されたオブジェクトごとの疑似乱数ジェネレーターを提供します。同じテンプレート パラメーターと同じ初期化パラメーターを使用すると、C++11 準拠の標準ライブラリを使用して構築された任意のコンピューター上の任意のコンピューターで、異なるオブジェクトがまったく同じオブジェクトごとの出力シーケンスを生成します。このクラスの利点は、予測可能な高品質の出力シーケンスと、実装全体での完全な一貫性です。

また、C++11 標準で定義されている PRNG エンジンがさらにあります - std::linear_congruential_engine<>srand/rand (一部の C 標準ライブラリ実装で公正な品質のアルゴリズムとして歴史的に使用されています) およびstd::subtract_with_carry_engine<>。また、完全に定義されたパラメーター依存のオブジェクトごとの出力シーケンスも生成します。

上記の廃止された C コードを現代の C++11 に置き換える例:

#include <iostream>
#include <chrono>
#include <random>

int main()
{
    std::random_device rd;
    // seed value is designed specifically to make initialization
    // parameters of std::mt19937 (instance of std::mersenne_twister_engine<>)
    // different across executions of application
    std::mt19937::result_type seed = rd() ^ (
            (std::mt19937::result_type)
            std::chrono::duration_cast<std::chrono::seconds>(
                std::chrono::system_clock::now().time_since_epoch()
                ).count() +
            (std::mt19937::result_type)
            std::chrono::duration_cast<std::chrono::microseconds>(
                std::chrono::high_resolution_clock::now().time_since_epoch()
                ).count() );

    std::mt19937 gen(seed);

    for( unsigned long j = 0; j < 100500; ++j )
    /* ^^^Yes. Generating single pseudo-random number makes no sense
       even if you use std::mersenne_twister_engine instead of rand()
       and even when your seed quality is much better than time(NULL) */    
    {
        std::mt19937::result_type n;
        // reject readings that would make n%6 non-uniformly distributed
        while( ( n = gen() ) > std::mt19937::max() -
                                    ( std::mt19937::max() - 5 )%6 )
        { /* bad value retrieved so get next one */ }

        std::cout << n << '\t' << n % 6 + 1 << '\n';
    }

    return 0;
}

std::uniform_int_distribution<>を使用する以前のコードのバージョン

#include <iostream>
#include <chrono>
#include <random>

int main()
{
    std::random_device rd;
    std::mt19937::result_type seed = rd() ^ (
            (std::mt19937::result_type)
            std::chrono::duration_cast<std::chrono::seconds>(
                std::chrono::system_clock::now().time_since_epoch()
                ).count() +
            (std::mt19937::result_type)
            std::chrono::duration_cast<std::chrono::microseconds>(
                std::chrono::high_resolution_clock::now().time_since_epoch()
                ).count() );

    std::mt19937 gen(seed);
    std::uniform_int_distribution<unsigned> distrib(1, 6);

    for( unsigned long j = 0; j < 100500; ++j )
    {
        std::cout << distrib(gen) << ' ';
    }

    std::cout << '\n';
    return 0;
}
于 2012-11-19T00:09:42.257 に答える
16

random number generationC++ プログラミング言語で基本的な Web 検索を行うと、通常、この質問が最初に表示されます。Web でこの同じ質問を必然的に検索する将来のコーダーのために、C++ での疑似乱数生成の概念をより明確にするために、リングに脱帽したいと思います!

基礎

疑似乱数の生成には、決定論的アルゴリズムを利用するプロセスが含まれます。このアルゴリズムは、プロパティがほぼ乱数に似ている一連の数値を生成します。真のランダム性は、数学とコンピューター サイエンスではとらえどころのない謎であるため、ほぼ に似ていると言います。したがって、疑似乱数という用語が、より衒学的に正しいために使用されるのはなぜでしょう。

実際に PRNG、つまり を使用する前に、シードpseudo-random number generatorとも呼ばれる初期値をアルゴリズムに提供する必要があります。ただし、アルゴリズム自体を使用する前に、シードを一度だけ設定する必要があります。

/// Proper way!
seed( 1234 ) /// Seed set only once...
for( x in range( 0, 10) ):
  PRNG( seed ) /// Will work as expected

/// Wrong way!
for( x in rang( 0, 10 ) ):
  seed( 1234 ) /// Seed reset for ten iterations!
  PRNG( seed ) /// Output will be the same...

したがって、適切な数列が必要な場合は、PRNG に十分なシードを提供する必要があります。

オールド C ウェイ

C++ が持つ後方互換性のある C の標準ライブラリは、ヘッダー ファイルにある線形合同ジェネレーターと呼ばれるものを使用します。cstdlibこの PRNG は、剰余算術を利用する不連続区分関数、つまりmodulo operator '%'. 以下は、@Predictability によって尋ねられた元の質問に関して、この PRNG の一般的な使用法です。

#include <iostream>
#include <cstdlib>
#include <ctime>

int main( void )
{
  int low_dist  = 1;
  int high_dist = 6;
  std::srand( ( unsigned int )std::time( nullptr ) );
  for( int repetition = 0; repetition < 10; ++repetition )
    std::cout << low_dist + std::rand() % ( high_dist - low_dist ) << std::endl;
  return 0;
}

C の PRNG の一般的な使用法には、次のような多くの問題があります。

  1. の全体的なインターフェイスはstd::rand()、特定の範囲内の疑似乱数を適切に生成するのにあまり直感的ではありません。たとえば、@Predictability が望んでいた方法で [1, 6] の間の数値を生成します。
  2. の一般的な使用法は、ピジョンホールの原理により、疑似乱数の一様分布std::rand()の可能性を排除します。
  3. は制限されたタイプと見なされるため、技術的std::rand()にシードされる一般的な方法std::srand( ( unsigned int )std::time( nullptr ) )は正しくありません。したがって、 からへの変換は保証されません。time_ttime_tunsigned int

C の PRNG を使用する際の全体的な問題と、それらを回避する方法の詳細については、rand() の使用 (C/C++): C 標準ライブラリの rand() 関数のアドバイスを参照してください。

標準的な C++ の方法

ISO/IEC 14882:2011 標準、つまり C++11 が公開されて以来、randomライブラリはしばらくの間 C++ プログラミング言語から離れていました。このライブラリには、複数のPRNG と、一様分布正規分布二項分布などのさまざまな分布タイプが装備されています。次のソース コード例は、@Predictability の元の質問に関して、ライブラリの非常に基本的な使用法を示しています。random

#include <iostream>
#include <cctype>
#include <random>

using u32    = uint_least32_t; 
using engine = std::mt19937;

int main( void )
{
  std::random_device os_seed;
  const u32 seed = os_seed();

  engine generator( seed );
  std::uniform_int_distribution< u32 > distribute( 1, 6 );

  for( int repetition = 0; repetition < 10; ++repetition )
    std::cout << distribute( generator ) << std::endl;
  return 0;
}

上記の例では、整数値が均一に分散された32 ビットのMersenne Twisterエンジンが使用されました。(ソース コードでのエンジンの名前は奇妙に聞こえます。その名前は2^19937-1 の期間に由来するためです)。この例では、 を使用してエンジンをシードし、オペレーティング システムから値を取得します (Linux システムを使用している場合は、 から値を返します)。std::random_devicestd::random_device/dev/urandom

エンジンstd::random_deviceをシードするために を使用する必要がないことに注意してください。定数やライブラリを使用することもできます! また、エンジンの 32 ビット バージョンを使用する必要はありません。他にもオプションがあります。ライブラリの機能の詳細については、 cplusplus.comを参照してください。chronostd::mt19937random

全体として、C++ プログラマーはstd::rand()もう使用すべきではありません。これは、 が悪いからではなく、現在の標準がより単純信頼できるより優れた代替手段を提供しているからです。願わくば、多くの人、特に最近ウェブ検索を行った人にとって、これが役立つことを願っていますgenerating random numbers in c++

于 2020-06-14T22:21:36.337 に答える
12

ブーストライブラリを使用している場合は、次の方法でランダム ジェネレーターを取得できます。

#include <iostream>
#include <string>

// Used in randomization
#include <ctime>
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_int_distribution.hpp>
#include <boost/random/variate_generator.hpp>

using namespace std;
using namespace boost;

int current_time_nanoseconds(){
    struct timespec tm;
    clock_gettime(CLOCK_REALTIME, &tm);
    return tm.tv_nsec;
}

int main (int argc, char* argv[]) {
    unsigned int dice_rolls = 12;
    random::mt19937 rng(current_time_nanoseconds());
    random::uniform_int_distribution<> six(1,6);

    for(unsigned int i=0; i<dice_rolls; i++){
        cout << six(rng) << endl;
    }
}

関数current_time_nanoseconds()は、シードとして使用される現在の時間をナノ秒単位で提供します。


範囲内のランダムな整数と日付を取得する、より一般的なクラスを次に示します。

#include <iostream>
#include <ctime>
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_int_distribution.hpp>
#include <boost/random/variate_generator.hpp>
#include "boost/date_time/posix_time/posix_time.hpp"
#include "boost/date_time/gregorian/gregorian.hpp"


using namespace std;
using namespace boost;
using namespace boost::posix_time;
using namespace boost::gregorian;


class Randomizer {
private:
    static const bool debug_mode = false;
    random::mt19937 rng_;

    // The private constructor so that the user can not directly instantiate
    Randomizer() {
        if(debug_mode==true){
            this->rng_ = random::mt19937();
        }else{
            this->rng_ = random::mt19937(current_time_nanoseconds());
        }
    };

    int current_time_nanoseconds(){
        struct timespec tm;
        clock_gettime(CLOCK_REALTIME, &tm);
        return tm.tv_nsec;
    }

    // C++ 03
    // ========
    // Dont forget to declare these two. You want to make sure they
    // are unacceptable otherwise you may accidentally get copies of
    // your singleton appearing.
    Randomizer(Randomizer const&);     // Don't Implement
    void operator=(Randomizer const&); // Don't implement

public:
    static Randomizer& get_instance(){
        // The only instance of the class is created at the first call get_instance ()
        // and will be destroyed only when the program exits
        static Randomizer instance;
        return instance;
    }
    bool method() { return true; };

    int rand(unsigned int floor, unsigned int ceil){
        random::uniform_int_distribution<> rand_ = random::uniform_int_distribution<> (floor,ceil);
        return (rand_(rng_));
    }

    // Is not considering the millisecons
    time_duration rand_time_duration(){
        boost::posix_time::time_duration floor(0, 0, 0, 0);
        boost::posix_time::time_duration ceil(23, 59, 59, 0);
        unsigned int rand_seconds = rand(floor.total_seconds(), ceil.total_seconds());
        return seconds(rand_seconds);
    }


    date rand_date_from_epoch_to_now(){
        date now = second_clock::local_time().date();
        return rand_date_from_epoch_to_ceil(now);
    }

    date rand_date_from_epoch_to_ceil(date ceil_date){
        date epoch = ptime(date(1970,1,1)).date();
        return rand_date_in_interval(epoch, ceil_date);
    }

    date rand_date_in_interval(date floor_date, date ceil_date){
        return rand_ptime_in_interval(ptime(floor_date), ptime(ceil_date)).date();
    }

    ptime rand_ptime_from_epoch_to_now(){
        ptime now = second_clock::local_time();
        return rand_ptime_from_epoch_to_ceil(now);
    }

    ptime rand_ptime_from_epoch_to_ceil(ptime ceil_date){
        ptime epoch = ptime(date(1970,1,1));
        return rand_ptime_in_interval(epoch, ceil_date);
    }

    ptime rand_ptime_in_interval(ptime floor_date, ptime ceil_date){
        time_duration const diff = ceil_date - floor_date;
        long long gap_seconds = diff.total_seconds();
        long long step_seconds = Randomizer::get_instance().rand(0, gap_seconds);
        return floor_date + seconds(step_seconds);
    }
};
于 2015-02-03T17:07:06.437 に答える
9
#include <iostream>
#include <cstdlib>
#include <ctime>

int main() {
    srand(time(NULL));
    int random_number = std::rand(); // rand() return a number between ​0​ and RAND_MAX
    std::cout << random_number;
    return 0;
}

http://en.cppreference.com/w/cpp/numeric/random/rand

于 2015-01-13T22:16:04.263 に答える
2

これが解決策です。乱数を返す関数を作成し、メイン関数の外に配置してグローバルにします。お役に立てれば

#include <iostream>
#include <cstdlib>
#include <ctime>
int rollDie();
using std::cout;
int main (){
    srand((unsigned)time(0));
    int die1;
    int die2;
    for (int n=10; n>0; n--){
    die1 = rollDie();
    die2 = rollDie();
    cout << die1 << " + " << die2 << " = " << die1 + die2 << "\n";
}
system("pause");
return 0;
}
int rollDie(){
    return (rand()%6)+1;
}
于 2014-05-08T20:44:16.533 に答える
1

RUNファイルごとにランダム

size_t randomGenerator(size_t min, size_t max) {
    std::mt19937 rng;
    rng.seed(std::random_device()());
    //rng.seed(std::chrono::high_resolution_clock::now().time_since_epoch().count());
    std::uniform_int_distribution<std::mt19937::result_type> dist(min, max);

    return dist(rng);
}
于 2016-02-01T11:49:41.910 に答える
0

ヘッダー、コンパイラ組み込み関数などを使用せずに C++ で乱数を生成する方法を知っています。

#include <cstdio> // Just for printf
int main() {
    auto val = new char[0x10000];
    auto num = reinterpret_cast<unsigned long long>(val);
    delete[] val;
    num = num / 0x1000 % 10;
    printf("%llu\n", num);
}

しばらく実行した後、次の統計を取得しました。

0: 5268
1: 5284
2: 5279
3: 5242
4: 5191
5: 5135
6: 5183
7: 5236
8: 5372
9: 5343

ランダムに見えます。

使い方:

  • 最新のコンパイラは、ASLR (アドレス空間レイアウトのランダム化) を使用してバッファー オーバーフローからユーザーを保護します。
  • したがって、ライブラリを使用せずにいくつかの乱数を生成できますが、それは単なる楽しみです。そのように ASLR を使用しないでください。
于 2020-12-24T17:41:39.730 に答える
-2

これは、およその単純な乱数発生器です。0付近で正と負の値を生成する確率が等しい:

  int getNextRandom(const size_t lim) 
  {
        int nextRand = rand() % lim;
        int nextSign = rand() % lim;
        if (nextSign < lim / 2)
            return -nextRand;
        return nextRand;
  }


   int main()
   {
        srand(time(NULL));
        int r = getNextRandom(100);
        cout << r << endl;
        return 0;
   }
于 2018-06-13T13:06:48.513 に答える