Overview
I've been playing with an idea for generating random numbers, given a mean value and standard deviation. I wanted to come up with a simple formula that was easier to understand (and remember) than the usual Box-Muller or Ziggurat algorithms.
The idea here is, by multiplying two uniform random numbers together, we can get something pretty close to a normal curve, with the exact desired standard deviation.
For the formulas below, $d$ is desired standard deviation, $m$ is desired mean, and $\operatorname{random}$ generates a random number with uniform distribution in the range $[0,1)$.
"Soft knee" formula: $$ \left(\operatorname{random}\left(\right)-\frac{1}{2}\right)2d3\operatorname{random}\left(\right)+m $$
"Hard knee" formula: $$ \left(\operatorname{random}\left(\right)-\frac{1}{2}\right)2d\sqrt{6\operatorname{random}\left(\right)}+m $$
Here is some Wolfram Language code to demonstrate the idea. This can be dropped into a Wolfram Notebook to test it out.
stdev=123.45;
softknee = TransformedDistribution[x1*x2,{
x1 \[Distributed] UniformDistribution[{-stdev, stdev}],
x2 \[Distributed] UniformDistribution[{0,3}]
}];
hardknee = TransformedDistribution[x1*x2,{
x1 \[Distributed] UniformDistribution[{-stdev, stdev}],
x2 \[Distributed] TransformedDistribution[Sqrt[x3],
{ x3 \[Distributed] UniformDistribution[{0,6}] }]
}];
Print["stdev desired: ", stdev,
", softknee: ", StandardDeviation[softknee],
", hardknee: ", StandardDeviation[hardknee]]
Plot[Table[CDF[softknee, x], {i,1}] //Evaluate,
{x, -4*stdev, 4*stdev}, PlotLabel->"Soft Knee"]
Plot[Table[CDF[hardknee, x], {i,1}] //Evaluate,
{x, -4*stdev, 4*stdev}, PlotLabel->"Hard Knee"]
Plot[Table[CDF[NormalDistribution[0,stdev],x], {i,1}] //Evaluate,
{x, -4*stdev, 4*stdev}, PlotLabel->"Normal Distribution"]
Implementation
Here's an implementation in Lua, as an example. The ^
operator performs exponentiation.
function softKnee(mean, stdev)
return (math.random() - 0.5) * 2 * stdev
* (math.random() * 3) + mean
end
function hardKnee(mean, stdev)
return (math.random() - 0.5) * 2 * stdev
* (math.random() * 6) ^ 0.5 + mean
end
Questions
Is the math working the way I think it's working, or did I screw something up (especially in the Wolfram test code)?
Can something similar be done to more closely approximate a normal curve, by tweaking some of the values?
Is this approach at all interesting, or should I scrap it and go with the usual suspects instead? I plan to use this in games, for example to determine where the pellets from a shotgun should go, so "feel" is more important than perfect accuracy here.
I like that the current implementations don't require any internal state, but thought about reusing one of the previous random values on each subsequent call, to eliminate overhead from calling
random
twice per "roll." Does that sound legit?