質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.24%

C++11のrandomの種

解決済

回答 3

投稿

  • 評価
  • クリップ 1
  • VIEW 2,655

BeatStar

score 1777

C/C++ ( いわゆる BetterC ) でやっています。

"C 標準の srand/rand で乱数発行は あまりよくない" というサイトページを何度か見かけたことがあります。

理由は

  • グローバル変数を使っていること

  • 乱数の範囲が固定 ( randで発行できる最大値が 決まっている )

らしいです。

なので、Boost の random を使おうと思い 質問したところ、

「C++11 のrandom を使った方がいい」というアドバイスをもらいました。

なので、「C++11 random」で検索してヒットした「cpprefjp - C++日本語リファレンス」

というサイトのサンプルコードを参考 ( ほぼ流用? ) して以下のクラスを作成しました。

// CRandom.h

// ここにインクルードガードがあるとして。

#include<string>
#include<time.h>
#include<random>


class CRandom{
      public:
                CRandom( double min, double max );
                ~CRandom();

                double RandDouble( void );
                int    RandInt( void );
      private:
                 std::random_device seed_gen; // シード
      private:
                 double               min;
                 double               max;
};
// CRandom.cpp

#include"CRandom.h"

using namespace std;


// コンストラクタ。取りうる 最小値と最大値 を受け取り、メンバ変数に格納
CRandom::CRandom( double min, double max ){
           this->min = min;
           this->max = max;
}

// デストラクタ。今のところ空。
CRandom::~CRandom(){

}

// double型として乱数を発行する
double CRandom::RandDouble( void ){
       default_random_engine engine( seed_gen() );

       // 少数として生成する
       uniform_real_distribution<> dist( min, max );

return dist( engine );
}

// int型として乱数を発行する
int CRandom::RandInt( void ){
    default_random_engine engine( seed_gen() );

    // 整数として生成する
    uniform_int_distribution<> dist( (int)min, (int)max );

return dist( engine );
}

として、

main関数がある main.cpp に

// main関数内として。

int r; // 乱数

CRandom* Random;

// 乱数の範囲は 0 - 4 とする
Random = new CRandom( 0, 4 );

for( int i = 0; i < 10; i++ ){
      // 乱数発行
      r = Random->RandInt();

      cout << r << endl;
}

delete Random;

としてコンパイル&実行したところ、

1
4
2
4
3
3
1
4
1
3

となりました。

ここまではいいのですが、何度起動しても同じ組み合わせです。

これを srand( time( NULL ) ); で初期化したような感じで、

起動毎に出てくる数値を変えたいのです。

たとえば、

起動一回目:
1
4
2
4
3
3
1
4
1
3

起動二回目:
2
5
6
2
3
3
4
1
0
0

...

という感じにしたいのです。

どこを修正すればいいのやら...

C++11 は使い慣れていない ( というより、C++ 自体使いきれていてない。 ) ので、

宜しくお願い致します。

[環境等]
言語: C/C++ ( C++11 )
コンパイラ: MinGW ( g++ )

宜しくお願い致します。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

+3

こんにちは。

原因は、MinGWの実装が期待と異なることですね。
「cpprefjp - C++日本語リファレンス」に記載されている「実装の制限によって予測不能な乱数生成器を定義できない場合、このクラスは擬似乱数生成器で定義される可能性がある。」にMinGWは残念ながら該当するようです。

C++ MinGWを使用した場合にstd::random_deviceが毎回同じ値を出力する問題について
Why do I get the same sequence for every run with std::random_device with mingw gcc4.8.1?

安易な対策は下記と思います。

default_random_engine engine( time(nullptr) );

ところで、乱数を生成する度にエンジンを初期化されているので上記対策をそのまま入れると複数個の乱数が事実上同じ種で初期化されるため、同じ値になります。乱数の初期化はコンストラクタでやって下さい。

更に原因は分かりませんが手元のMinGW 5.4.0でやったところ、何故か毎回最初の1個目が1になります。かなり偏りが激しいのかも知れません。
乱数の初期化についてはどなたか詳しい方がいらっしゃるのを待った方が良いと思います。(私は乱数には詳しくないです。)

後、型だけが異なるものを作る時はテンプレートを活用するとよいです。
スマートに書くには意外にC++11の機能を使いまくることになりそうですが。
後でサンプルを書いてみるかも知れません。(期待はしないで下さい。)


【追記】
サンプル作ってみました。
でもすいません。「型だけが異なるものを作る時はテンプレートを活用するとよいです」の効果が見える姿にできませんでした。
uniform_real_distribution<>とuniform_int_distribution<>を分岐させるために結局2つほぼ同じものを定義すると言うおまぬけです。コンパイル時処理になるのでそこそこ軽くはなっている筈ですが。

# include <iostream>
# include <random>
# include <type_traits>
# include <ctime>

template<typename tType, class tEnable=void>
class Random
{
    std::default_random_engine              mEngine;
    std::uniform_real_distribution<tType>   mDistribution;
public:
    Random(tType iMin, tType iMax) :
        mEngine(time(nullptr)),
        mDistribution(iMin, iMax)
    { }

    tType operator()()
    {
        return mDistribution(mEngine);
    }
};

template<typename tType>
class Random<tType, typename std::enable_if<std::is_integral<tType>::value>::type>
{
    std::default_random_engine              mEngine;
    std::uniform_int_distribution<tType>    mDistribution;
public:
    Random(tType iMin, tType iMax) :
        mEngine(time(nullptr)),
        mDistribution(iMin, iMax)
    { }

    tType operator()()
    {
        return mDistribution(mEngine);
    }
};

int main()
{
    Random<int> aRandomInt(0, 4);
    for(int i=0; i < 10; ++i)
    {
        std::cout << aRandomInt() << '\n';
    }

    Random<double> aRandomDouble(0, 4);
    for(int i=0; i < 10; ++i)
    {
        std::cout << aRandomDouble() << '\n';
    }

    return 0;
}

tTypeが整数(integral)の時後者のRandomクラス、それ以外の時前者のRandomクラスが実体化されます。


【michiru_cppさんのアイデアを貰って修正】

# include <iostream>
# include <random>
# include <type_traits>
# include <ctime>

template<typename tType>
class Random
{
    std::default_random_engine              mEngine;
    typename
        std::conditional
        <
            std::is_integral<tType>::value,
            std::uniform_int_distribution<tType>,
            std::uniform_real_distribution<tType>
        >::type                             mDistribution;
public:
    Random(tType iMin, tType iMax) :
        mEngine(time(nullptr)),
        mDistribution(iMin, iMax)
    { }

    tType operator()()
    {
        return mDistribution(mEngine);
    }
};

int main()
{
    Random<int> aRandomInt(0, 4);
    for(int i=0; i < 10; ++i)
    {
        std::cout << aRandomInt() << '\n';
    }

    Random<double> aRandomDouble(0, 4);
    for(int i=0; i < 10; ++i)
    {
        std::cout << aRandomDouble() << '\n';
    }

    return 0;
}

よし、同じことを2回書かずにできました。
なるほどstd::conditional<>強力です。

BeatStarさん。質問の主旨から外してしまってすいません。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/12/23 13:06 編集

    帰ったら調べてみるか、と思ったらすでに回答が3件ついていたので(ぁ
    せめて改良してみましたw
    http://ideone.com/W8K68u

    >質問主さん
    他でもさんざん書かれてますが、乱数を繰り返し取り出すにはメルセンヌツイスタがベストだと思います(利点は検索すればいっぱい出てきます)
    random_deviceは、ちゃんと実装されてる処理系でも、種を作るとき
    (srand( time( NULL ) );におけるtime(NULL))に使うのが普通かもしれません。(失礼、元からそう使ってましたね)
    「time(0)を使うのはおすすめしない」、という記述も見かけましたが
    random_deviceが使えないとあればtime(0)とかでいいと思います

    追記:default_random_engineはコンパイラによって違いますが、gccやclangでは
    rand()と同じ線形合同法です(品質の改善はあるようですが)

    キャンセル

  • 2016/12/23 13:30

    michiru_cppさん、ありがとうございます。

    なるほど!! std::conditional<>を使うとスマートにかけますね。
    しかも、プライマリだけで済むのでクラス内に入れてもすっきり書けそうですね。そして、クラス内ならprivateにできるので更にすっきりしそうです。(private namespaceが無いのが悔しい。)

    boostで何度も見かけていたのに、なんでこんなに読みにくいことしてるんだろうと思ってました。実はかなり良くなっているんですね。気が付かなかった...

    良いことを教わりました。ありがとうです。

    キャンセル

  • 2016/12/23 13:47

    ありがとうございますm(_ _)m
    >private namespaceが無いのが悔しい
    なんかわかります、せめてdetailの部分はヘッダ別けた方が良いかもですね

    キャンセル

checkベストアンサー

0

乱数生成器の初期化(初期シードの指定)は1回だけでよいです。
以下の例では乱数生成器をメンバ変数にしてシードの指定をコンストラクタで行っています。

#include <random>
using namespace std;

class CRand{
    default_random_engine m_engine;
    uniform_int_distribution<> m_dice;
public:
    CRand( int min = 0, int max = 0):m_dice(min,max){
        // srandに相当
        random_device rd;
        default_random_engine e(rd());
        m_engine = e;
    }
    int RandInt( void){    return m_dice(m_engine);}
};

int main() {
    CRand r(1,100);
    for(int i=0; i<10; ++i){
        std::cout << r.RandInt() << '\n';
    }
    return 0;
}

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

0

これを srand( time( NULL ) ); で初期化したような感じで、 
起動毎に出てくる数値を変えたいのです。 

できますよ。
メルセンヌ・ツイスタを使った例だと:

  std::mt19937 gen(time(nullptr)); // メルセンヌ・ツイスタ
  std::normal_distribution<double> dist(5.0, 3.0); // 平均5.0/標準偏差3.0の正規分布
  auto rand = [&]() { return dist(gen); }

  // 10回生成してみる
  for ( int i = 0; i < 10; ++i ) {
    std::cout << rand() << std::endl;
  }

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.24%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る