1

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?

  • Moderators: I'm not really sure if this is the right site for this. If not, feel free to move it. – user11536834 Jul 08 '19 at 19:41
  • Note that the variance of a uniform distribution on $[0,1]$ is $\frac1{12}$. So I don't quite understand where you get the $\sqrt 6$ or $3$ from? – Maximilian Janisch Jul 08 '19 at 20:06
  • Another comment: The product of two uniformly distributed random variables over $[0,1]$ has the PDF $f(x)=-\ln x \cdot 1_{[0,1]}(x)$. I am not sure if you still get can get to something close to a normal distribution based on this. – Maximilian Janisch Jul 08 '19 at 20:14
  • @MaximilianJanisch to be honest, I was working on this a few weeks ago and have forgotten some of the details of how I arrived at some of those values. But it may help to think of the random number being in the range [-1, 1] -- that is, the $\left(\operatorname{random}\left(\right)-\frac{1}{2}\right)2$ bit taken by itself. I'll try to work though it again later and update the question with some background info on how I arrived at these values. – user11536834 Jul 08 '19 at 20:30
  • Is this really any easier to remember or understand than the Box-Muller transformation? I would just use the latter and be done with it. Better yet, use a library function to generate normal random numbers directly, if the target language provides one. Even C++ provides this as part of the standard library these days. –  Jul 08 '19 at 20:32
  • @Bungo I think so, but I'm prepared to be persuaded otherwise. If that fourth bullet point passes the smell test, the "soft" version should also be significantly faster to compute, and maybe the "hard" version also. – user11536834 Jul 08 '19 at 20:45

0 Answers0