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

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

新規登録して質問してみよう
ただいま回答率
85.48%
C++11

C++11は2011年に容認されたC++のISO標準です。以前のC++03に代わるもので、中枢の言語の変更・修正、標準ライブラリの拡張・改善を加えたものです。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

解決済

3回答

5355閲覧

C++11のrandomの種

BeatStar

総合スコア4958

C++11

C++11は2011年に容認されたC++のISO標準です。以前のC++03に代わるもので、中枢の言語の変更・修正、標準ライブラリの拡張・改善を加えたものです。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

0グッド

1クリップ

投稿2016/12/23 02:10

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

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

理由は

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

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

らしいです。

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

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

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

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

C++

1// CRandom.h 2 3// ここにインクルードガードがあるとして。 4 5#include<string> 6#include<time.h> 7#include<random> 8 9 10class CRandom{ 11 public: 12 CRandom( double min, double max ); 13 ~CRandom(); 14 15 double RandDouble( void ); 16 int RandInt( void ); 17 private: 18 std::random_device seed_gen; // シード 19 private: 20 double min; 21 double max; 22}; 23

C++

1// CRandom.cpp 2 3#include"CRandom.h" 4 5using namespace std; 6 7 8// コンストラクタ。取りうる 最小値と最大値 を受け取り、メンバ変数に格納 9CRandom::CRandom( double min, double max ){ 10 this->min = min; 11 this->max = max; 12} 13 14// デストラクタ。今のところ空。 15CRandom::~CRandom(){ 16 17} 18 19// double型として乱数を発行する 20double CRandom::RandDouble( void ){ 21 default_random_engine engine( seed_gen() ); 22 23 // 少数として生成する 24 uniform_real_distribution<> dist( min, max ); 25 26return dist( engine ); 27} 28 29// int型として乱数を発行する 30int CRandom::RandInt( void ){ 31 default_random_engine engine( seed_gen() ); 32 33 // 整数として生成する 34 uniform_int_distribution<> dist( (int)min, (int)max ); 35 36return dist( engine ); 37}

として、

main関数がある main.cpp に

C++

1// main関数内として。 2 3int r; // 乱数 4 5CRandom* Random; 6 7// 乱数の範囲は 0 - 4 とする 8Random = new CRandom( 0, 4 ); 9 10for( int i = 0; i < 10; i++ ){ 11 // 乱数発行 12 r = Random->RandInt(); 13 14 cout << r << endl; 15} 16 17delete Random; 18

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

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++ )

宜しくお願い致します。

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

0

こんにちは。

原因は、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つほぼ同じものを定義すると言うおまぬけです。コンパイル時処理になるのでそこそこ軽くはなっている筈ですが。

C++

1# include <iostream> 2# include <random> 3# include <type_traits> 4# include <ctime> 5 6template<typename tType, class tEnable=void> 7class Random 8{ 9 std::default_random_engine mEngine; 10 std::uniform_real_distribution<tType> mDistribution; 11public: 12 Random(tType iMin, tType iMax) : 13 mEngine(time(nullptr)), 14 mDistribution(iMin, iMax) 15 { } 16 17 tType operator()() 18 { 19 return mDistribution(mEngine); 20 } 21}; 22 23template<typename tType> 24class Random<tType, typename std::enable_if<std::is_integral<tType>::value>::type> 25{ 26 std::default_random_engine mEngine; 27 std::uniform_int_distribution<tType> mDistribution; 28public: 29 Random(tType iMin, tType iMax) : 30 mEngine(time(nullptr)), 31 mDistribution(iMin, iMax) 32 { } 33 34 tType operator()() 35 { 36 return mDistribution(mEngine); 37 } 38}; 39 40int main() 41{ 42 Random<int> aRandomInt(0, 4); 43 for(int i=0; i < 10; ++i) 44 { 45 std::cout << aRandomInt() << '\n'; 46 } 47 48 Random<double> aRandomDouble(0, 4); 49 for(int i=0; i < 10; ++i) 50 { 51 std::cout << aRandomDouble() << '\n'; 52 } 53 54 return 0; 55}

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


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

C++

1# include <iostream> 2# include <random> 3# include <type_traits> 4# include <ctime> 5 6template<typename tType> 7class Random 8{ 9 std::default_random_engine mEngine; 10 typename 11 std::conditional 12 < 13 std::is_integral<tType>::value, 14 std::uniform_int_distribution<tType>, 15 std::uniform_real_distribution<tType> 16 >::type mDistribution; 17public: 18 Random(tType iMin, tType iMax) : 19 mEngine(time(nullptr)), 20 mDistribution(iMin, iMax) 21 { } 22 23 tType operator()() 24 { 25 return mDistribution(mEngine); 26 } 27}; 28 29int main() 30{ 31 Random<int> aRandomInt(0, 4); 32 for(int i=0; i < 10; ++i) 33 { 34 std::cout << aRandomInt() << '\n'; 35 } 36 37 Random<double> aRandomDouble(0, 4); 38 for(int i=0; i < 10; ++i) 39 { 40 std::cout << aRandomDouble() << '\n'; 41 } 42 43 return 0; 44}

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

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

投稿2016/12/23 02:56

編集2016/12/23 05:29
Chironian

総合スコア23272

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2016/12/23 07:23 編集

帰ったら調べてみるか、と思ったらすでに回答が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()と同じ線形合同法です(品質の改善はあるようですが)
Chironian

2016/12/23 04:30

michiru_cppさん、ありがとうございます。 なるほど!! std::conditional<>を使うとスマートにかけますね。 しかも、プライマリだけで済むのでクラス内に入れてもすっきり書けそうですね。そして、クラス内ならprivateにできるので更にすっきりしそうです。(private namespaceが無いのが悔しい。) boostで何度も見かけていたのに、なんでこんなに読みにくいことしてるんだろうと思ってました。実はかなり良くなっているんですね。気が付かなかった... 良いことを教わりました。ありがとうです。
退会済みユーザー

退会済みユーザー

2016/12/23 04:47

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

0

ベストアンサー

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

C++

1#include <random> 2using namespace std; 3 4class CRand{ 5 default_random_engine m_engine; 6 uniform_int_distribution<> m_dice; 7public: 8 CRand( int min = 0, int max = 0):m_dice(min,max){ 9 // srandに相当 10 random_device rd; 11 default_random_engine e(rd()); 12 m_engine = e; 13 } 14 int RandInt( void){ return m_dice(m_engine);} 15}; 16 17int main() { 18 CRand r(1,100); 19 for(int i=0; i<10; ++i){ 20 std::cout << r.RandInt() << '\n'; 21 } 22 return 0; 23}

投稿2016/12/23 03:00

can110

総合スコア38266

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

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

投稿2016/12/23 02:58

episteme

総合スコア16614

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問