How to correctly use std::normal_distribution in C++11?
c++, c++11
Solution
Short answer: do not chop off the tails of the normal distribution.
Long answer: The problem is that with a standard deviation of 1 you have most values inside the interval [0,1]. If you take a look at the normal distribution:
The part you are using is very much at the center and you would need many more samples to detect a difference. Just cutting of values outside your range is absolutely not going to give you a normal distributed sample.
You can see that the cumulative densitiy function is almost linear in the [0,1] interval you are using:
Pictures generated with wolfram alpha.
At this zoom in the shape of the distribution is almost triangular, and you can check the output here for more samples:
#include <iostream>
#include <random>
using namespace std;
static std::random_device __randomDevice;
static std::mt19937 __randomGen(__randomDevice());
static std::normal_distribution<float> __normalDistribution(0.5, 1);
// Get a normally distributed float value in the range [0,1].
inline float GetNormDistrFloat()
{
float val = -1;
do { val = __normalDistribution(__randomGen); }
while(val < 0.0f || val > 1.0f);
return val;
}
int main() {
int count1=0;
int count2=0;
int count3=0;
int count4=0;
for (int i =0; i< 1000000; i++) {
float val = GetNormDistrFloat();
if (val<0.25){ count1++; continue;}
if (val<0.5){ count2++; continue;}
if (val<0.75){ count3++; continue;}
if (val<1){ count4++; continue;}
}
std::cout<<count1<<", "<<count2<<", "<<count3<<", "<<count4<<std::endl;
return 0;
}
Success time: 0.1 memory: 16072 signal:0
241395, 258131, 258275, 242199
First Option (suggested by Caleth): use (the) logistic function 1 / (1 + exp(-x)), which has a domain (−∞, +∞) and range [0,1]. This way you actually get the full normal distribution.
Another option: Its not as nice mathematically as the one above, but probably faster. You can use a standard normal distribution with mean 0 and deviation 1 and then remap to `[0,1]` from a much larger range such as +/- 4 standard deviations. Now you have the problem that the weight of your integral is not longer 1 but a little less. Its not actually a random variable anymore.
If you want to get a weight of 1, you can distribute the remaining tails (outside of 4 stds) by not rerolling but by getting a uniformly distributed random value from the `[0,1]` interval, this case:
val = NormalRand(0,1);
if abs(val) < 4 return val/8 + 0.5
else return UniformRand(0,1)
Another option (as suggested by interjay): simply decrease the standard deviation.
Problem
I want to get random float numbers in the range `[0.0,1.0]`, so most of these numbers should be around `0.5`. Thus I came up with the following function: ``` static std::random_device __randomDevice; static std::mt19937 __randomGen(__randomDevice()); static std::normal_distribution<float> __normalDistribution(0.5, 1); // Get a normally distributed float value in the range [0,1]. inline float GetNormDistrFloat() { float val = -1; do { val = __normalDistribution(__randomGen); } while(val < 0.0f || val > 1.0f); return val; } ``` However, calling that function 1000 times leads to the following distribution: ``` 0.0 - 0.25 : 240 times 0.25 - 0.5 : 262 times 0.5 - 0.75 : 248 times 0.75 - 1.0 : 250 times ``` I was expecting the first and last quarter of the range to show up much fewer than what is shown above. So it seems I am doing something wrong here. Any ideas?