39 votes

Ordre des nombres aléatoires en C ++ en utilisant <random>

J'ai le code suivant, que j'ai écrit pour tester une partie d'un vaste programme :

#include <fstream>
#include <random>
#include <iostream>
using namespace std ;

int main()
{
  mt19937_64 Generator(12187) ;
  mt19937_64 Generator2(12187) ;
  uniform_int_distribution<int> D1(1,6) ;

  cout << D1(Generator) << " " ;
  cout << D1(Generator) << " " << D1(Generator) << endl ;
  cout << D1(Generator2) << " " << D1(Generator2) << " " << D1(Generator2) << endl ;

  ofstream g1("g1.dat") ;
  g1 << Generator ;
  g1.close() ;
  ofstream g2("g2.dat") ;
  g2 << Generator2 ;
  g2.close() ;
}                                                            

Les deux générateurs sont ensemencés avec la même valeur, et donc j'ai attendu la deuxième ligne dans la sortie pour être identique à la première. Au lieu de cela, la sortie est

1 1 3
1 3 1

L'état des deux générateurs imprimés dans l' *.dat des fichiers est la même. Je me demandais si il pourrait y avoir quelques cachée du multi-threading dans la génération de nombre aléatoire provoquant l'ordre d'incompatibilité.

J'ai compilé avec g++ version 5.3.0, sur Linux, avec le drapeau de l' -std=c++11.

Merci d'avance pour votre aide.

42voto

Richard Hodges Points 1972

x << y est sucre syntaxique pour un appel à la fonction operator<<(x, y).

Vous vous souviendrez que le c++ standard n'impose aucune restriction sur l'ordre d'évaluation des arguments d'un appel de fonction.

Ainsi, le compilateur est libre d'émettre un code qui calcule premier x ou y d'abord.

À partir de la norme: §5 note 2:

Les opérateurs peuvent être surchargés, qui est, son sens lorsqu'il est appliqué à des expressions de type de classe (Clause 9) ou à un type d'énumération (7.2). Utilise des opérateurs surchargés sont transformés en appels de fonction comme décrit 13,5. Opérateurs surchargés de respecter les règles de syntaxe spécifiée dans la Clause 5, mais les exigences de opérande type, la valeur de la catégorie, et l'ordre d'évaluation sont remplacés par les règles de l'appel de fonction.

36voto

alain Points 1226

C'est parce que l'ordre d'évaluation de cette ligne

 cout << D1(Generator2) << " " << D1(Generator2) << " " << D1(Generator2) << endl ;
 

n'est pas ce que vous pensez.

Vous pouvez le tester avec ceci:

 int f() {
  static int i = 0;
  return i++;
}

int main() {
  cout << f() << " " << f() << " " << f() << endl ;
  return 0;
}
 

Sortie: 2 1 0


La commande n'est pas spécifiée par la norme C ++, elle pourrait donc être différente de celle des autres compilateurs. Veuillez consulter la réponse de Richard Hodges.

5voto

Anedar Points 2559

Un léger changement dans le programme révèle ce qui se passe:

 #include <fstream>
#include <random>
#include <iostream>
using namespace std ;

int main()
{
  mt19937_64 Generator(12187) ;
  mt19937_64 Generator2(12187) ;
  uniform_int_distribution<int> D1(1,100) ;

  cout << D1(Generator) << " " ;
  cout << D1(Generator) << " " ;
  cout << D1(Generator) << endl ;
  cout << D1(Generator2) << " " << D1(Generator2) << " " << D1(Generator2) << endl ;
}
 

Sortie:

 4 48 12
12 48 4
 

Ainsi, vos générateurs produisent des résultats égaux, mais l'ordre des arguments de votre ligne de commande est calculé dans un ordre différent.

Essayez-le en ligne: http://ideone.com/rsoqDe

4voto

Yam Marcovic Points 1566

Ces lignes

  cout << D1(Generator) << " " ;

  cout << D1(Generator) << " "
       << D1(Generator) << endl ;

  cout << D1(Generator2) << " "
       << D1(Generator2) << " "
       << D1(Generator2) << endl ;

parce qu' D1() renvoie un int, pour qui ostream::operator<<() a une surcharge, sont effectivement à l'appel (à l'exclusion endl)

cout.operator<<(D1(Generator));

cout.operator<<(D1(Generator))
    .operator<<(D1(Generator));

cout.operator<<(D1(Generator2))
    .operator<<(D1(Generator2))
    .operator<<(D1(Generator2));

Maintenant, le standard a ceci à dire,

§ 5.2.2 [4]

Quand une fonction est appelée, chaque paramètre doit être initialisé avec son argument correspondant.

[ Note: Ces initialisations sont pour une période indéterminée séquencé à l'égard les uns des autres - la note de fin ]

Si la fonction est un non-statique fonction de membre de la ce paramètre de la fonction est initialisé avec un pointeur vers l'objet de l'appel

Donc, nous allons décomposer l'expression précédente

cout.operator<<(a())  // #1
    .operator<<(b())  // #2
    .operator<<(c()); // #3

Pour illustrer la construction de l' this pointeur, ce sont conceptuellement équivalents à (en omettant ostream:: par souci de concision):

operator<<(           // #1
  &operator<<(        // #2
    &operator<<(      // #3
      &cout,
      a()
    ),                // end #3
    b()
  ),                  // end #2
  c()
);                    // end #1

Examinons maintenant le haut niveau de l'appel. Ce qui ne nous évaluons tout d'abord, #2ou c()? Car, comme l'a souligné dans la citation, l'ordre est de durée indéterminée, alors nous ne savons pas—et cela est vrai de manière récursive: même si nous avons évalué #2, nous serions toujours confrontés à la question de savoir si l'évaluation de ses internes #3 ou b().

Donc, qui nous l'espérons vous explique ce qui se passe ici plus clairement.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X