Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++11 cross compiler/standard library random distribution reproducibility

While the random engines are required to give the same sequence of numbers on each compiler. At least some of the random distributions are not, only requiring that they meet statistical and probabilistic thresholds. As an example:

#include <random>
#include <iostream>

int main() {
  std::mt19937 foo;
  std::uniform_int_distribution<int> bar(0, 1000);

  for (int i=0; i<99; ++i) {
    bar(foo);
  }

  std::cout << bar(foo) << std::endl;

  return 0;
}

Which will print 808 when compiled against (my version of) libstdc++ and 89 when compiled against libc++.

Which of the standard provided distribution functions, if any, are guaranteed to produce consistent results no matter what compliant environment you are given?

like image 405
OmnipotentEntity Avatar asked Oct 23 '14 22:10

OmnipotentEntity


1 Answers

Unfortunately, as of N3936 (C++14 final draft), no standard provided random distribution has a requirement like this. And it's easy to see why. There are many valid ways of writing a distribution function. Some better than others. And algorithms for even something as basic as the normal distribution are getting better and are the subject of active research. Mandating use of a single one would unnecessarily roadblock the implementation of future algorithms.

Fortunately, you can write your own. The specification for the headers of the various distribution classes are located under §26.5.8. But there is no reason why yours needs to follow this structure necessarily.

(Please note, I have not tested this code thoroughly, and there might be bad behavior with certain engines, or with overflow, though I have taken some pains to avoid the latter, this is intended more as an illustrative example than a canonical source of awesome uniform distribution. That being said, if you find anything wrong with it, let me know in the comments and I'll be happy to correct it.)

#include <random>
#include <tuple>
#include <iostream>

template<class IntType = int>
class my_uniform_int_distribution {
public:
  // types
  typedef IntType result_type;
  typedef std::pair<int, int> param_type;

  // constructors and reset functions
  explicit my_uniform_int_distribution(IntType a = 0, IntType b = std::numeric_limits<IntType>::max());
  explicit my_uniform_int_distribution(const param_type& parm);
  void reset();

  // generating functions
  template<class URNG>
    result_type operator()(URNG& g);
  template<class URNG>
    result_type operator()(URNG& g, const param_type& parm);

  // property functions
  result_type a() const;
  result_type b() const;
  param_type param() const;
  void param(const param_type& parm);
  result_type min() const;
  result_type max() const;

private:
  typedef typename std::make_unsigned<IntType>::type diff_type;

  IntType lower;
  IntType upper;
};

template<class IntType>
my_uniform_int_distribution<IntType>::my_uniform_int_distribution(IntType a, IntType b) {
  param({a, b});
}

template<class IntType>
my_uniform_int_distribution<IntType>::my_uniform_int_distribution(const param_type& parm) {
  param(parm);
}

template<class IntType>
void my_uniform_int_distribution<IntType>::reset() {}

template<class IntType>
template<class URNG>
auto my_uniform_int_distribution<IntType>::operator()(URNG& g) -> result_type {
  return operator()(g, param());
}

template<class IntType>
template<class URNG>
auto my_uniform_int_distribution<IntType>::operator()(URNG& g, const param_type& parm) -> result_type {
  diff_type diff = (diff_type)parm.second - (diff_type)parm.first + 1;
  if (diff == 0) // If the +1 overflows we are using the full range, just return g()
    return g();

  diff_type badDistLimit = std::numeric_limits<diff_type>::max() / diff;
  do {
    diff_type generatedRand = g();

    if (generatedRand / diff < badDistLimit)
      return (IntType)((generatedRand % diff) + (diff_type)parm.first);
  } while (true);
}

template<class IntType>
auto my_uniform_int_distribution<IntType>::a() const -> result_type {
  return lower;
}

template<class IntType>
auto my_uniform_int_distribution<IntType>::b() const -> result_type {
  return upper;
}

template<class IntType>
auto my_uniform_int_distribution<IntType>::param() const -> param_type {
  return {lower, upper};
}

template<class IntType>
void my_uniform_int_distribution<IntType>::param(const param_type& parm) {
  std::tie(lower, upper) = parm;
  if (upper < lower)
    throw std::exception();
}

template<class IntType>
auto my_uniform_int_distribution<IntType>::min() const -> result_type {
  return lower;
}

template<class IntType>
auto my_uniform_int_distribution<IntType>::max() const -> result_type {
  return upper;
}

int main() {
  std::mt19937 foo;
  my_uniform_int_distribution<int> bar(0,1000);

  for (int i=0; i<99; ++i) {
    bar(foo);
  }

  std::cout << bar(foo) << std::endl;

  return 0;
}

This code prints out 490 on all platforms I've tested.

like image 66
OmnipotentEntity Avatar answered Oct 07 '22 00:10

OmnipotentEntity