HiveBrain v1.2.0
Get Started
← Back to all entries
snippetcppMinor

Portably generate uniformly random floats from mt19937 output

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
randomuniformlyoutputgenerateportablyfromfloatsmt19937

Problem

My goal is to generate a sequence of random float from a seeded random generator. The sequence should be the same on any machine / any compiler -- this rules out the use of std::uniform_real_distribution since it doesn't make that guarantee. Instead we have to create our own version of this, which we will be able to guarantee is portable / uses the same implementation on all platforms.

We can assume I'm starting from std::mt19937 since the C++ standard does mandate its implementation, so the goal becomes, how can I convert a uniformly random uint32_t to a uniformly random float in the range [0.0f, 1.0f]. The main things I'm concerned about are:

  • Efficiency



  • Loss of precision



I threw something together which looks like this:

template 
float uniform_float(RNG & rng) {
  static_assert(std::is_same::value, "Expected to be used with RNG whose result type is uint32_t, like mt19937");

  float result = static_cast(rng());

  for (int i = 0; i < 32; ++i) {
    result /= 2;
  }
  return result;
}


In preliminary tests it seems to be outputting floats in the range [0.0f, 1.0f].

I did also try looking around in the libstdc++ headers to see where they are doing the equivalent thing, but it looked like it was going to take some digging to actually find it.

Here are some natural questions in my mind:

-
When static casting uint32_t to float, the value is never outside the representable range, so the behavior is not undefined. Typically it will not be representable exactly though, since both types have 32 bits, and float has to have some overhead. The standard says it is implementation-defined whether I get the next highest or next lowest representable number in this case. I assume that it doesn't matter since I'm going to divide by two anyways many times after this, and then many of these values will collide anyways?

-
Is it better (faster) to divide by the uint32_t max value, rather than by 2^32? I assume not.

-
Does divi

Solution

At least in my opinion, the efficiency question you've raised (repeated division by 2 vs. division by 232 vs. multiplication by 2-32) is best answered by profiling.

I, however, would take issue with making a blind assumption that the generator's maximum is 232-1 at all. Instead, I'd rather use the generator's own specification of the maximum value it can return, so the code would look something like this:

return (float)rng() / (float)RNG::max();


Here again, pre-inverting and multiplying might (possibly) improve speed:

static const float factor = 1.0f / RNG::max();

return rng() * factor;


I also tend to wonder whether it might make sense to make the result type generic. With this, we're no longer restricted to a generator that produces a 32-bit result, but we probably do want to assure that the result type is floating point.

template 
Result uniform_dist(RNG & rng) {
    static_assert(std::is_floating_point::value, "Result must be a floating point type");

    static const Result factor = Result(1) / static_cast(RNG::max());

    return rng() * factor;
}


Then we'd specify the result type as a template parameter:

std::mt19937 rng { std::random_device()() };

std::cout (rng) (rng) << "\n";

Code Snippets

return (float)rng() / (float)RNG::max();
static const float factor = 1.0f / RNG::max();

return rng() * factor;
template <typename Result, typename RNG>
Result uniform_dist(RNG & rng) {
    static_assert(std::is_floating_point<Result>::value, "Result must be a floating point type");

    static const Result factor = Result(1) / static_cast<Result>(RNG::max());

    return rng() * factor;
}
std::mt19937 rng { std::random_device()() };

std::cout << uniform_dist<float>(rng) << "\n";

std::cout << uniform_dist<double>(rng) << "\n";

Context

StackExchange Code Review Q#122497, answer score: 3

Revisions (0)

No revisions yet.