<p>Let me talk about random integer generating algorithms that are &quot;optimal&quot; in terms of the number of random bits it uses on average. In the rest of this post, we will assume we have a &quot;true&quot; random generator that can produce unbiased and independent random bits. (Here, a random &quot;byte&quot; will be a block of 8 random bits.)</p>
<p>In 1976, D. E. Knuth and A. C. Yao showed that any algorithm that produces random integers with a given probability, using only random bits, can be represented as a binary tree, where random bits indicate which way to traverse the tree and each leaf (endpoint) corresponds to an outcome. Knuth and Yao showed that any <em>optimal</em> binary tree algorithm for generating integers in <code>[0, n)</code> uniformly, will need <strong>at least <code>log2(n)</code> and at most <code>log2(n) + 2</code> bits on average</strong>. (Thus, even an <em>optimal</em> algorithm has a chance of &quot;wasting&quot; bits.) See below for examples of optimal algorithms.</p>
<p>However, any <em>optimal</em> integer generator that is also <em>unbiased</em> will, in general, run forever in the worst case, as also shown by Knuth and Yao. Going back to the binary tree, each one of the n outcomes labels leaves in the binary tree so that each integer in [0, n) can occur with probability 1/n. But if 1/n has a non-terminating binary expansion (which will be the case if n is not a power of 2), this binary tree will necessarily either—</p>
<ul>
<li>Have an &quot;infinite&quot; depth, or</li>
<li>include &quot;rejection&quot; leaves at the end of the tree,</li>
</ul>
<p>And in either case, the algorithm will run forever in the worst case, even if it uses very few random bits on average. (On the other hand, when n is a power of 2, the optimal binary tree will have no rejection nodes and require exactly n bits before returning an outcome, so that no bits will be &quot;wasted&quot;.) The Fast Dice Roller is an example of an algorithm that uses &quot;rejection&quot; events to ensure it's unbiased; see the comment in the code below.</p>
<p>Thus, in general, <strong>a random integer generator can be <em>either</em> unbiased <em>or</em> constant-time (or even neither), but not both.</strong> And the binary tree concept shows that there is no way in general to &quot;fix&quot; the worst case of an indefinite running time without introducing bias. For instance, modulo reductions (e.g., <code>rand() % n</code>) are equivalent to a binary tree in which rejection leaves are replaced with labeled outcomes — but since there are more possible outcomes than rejection leaves, only some of the outcomes can take the place of the rejection leaves, introducing bias. A similar kind of binary tree — and a similar kind of bias — results if you stop rejecting after a set number of iterations. (However, this bias may be negligible depending on the application. There are also security aspects to random integer generation, which are too complicated to discuss in this answer.)</p>
<h3>Fast Dice Roller Implementation</h3>
<p>There are many examples of <em>optimal</em> algorithms in the sense given earlier. One of them is the <a href="https://arxiv.org/abs/1304.1916" rel="nofollow noreferrer">Fast Dice Roller</a> by J. Lumbroso (2013) (implemented below), and perhaps other examples are the algorithm given as an <a href="https://stackoverflow.com/a/10481147/815724">answer to a similar <em>Stack Overflow</em> question</a> and the algorithm given in the <a href="http://mathforum.org/library/drmath/view/65653.html" rel="nofollow noreferrer">Math Forum</a> in 2004. On the other hand, all the algorithms <a href="https://www.pcg-random.org/posts/bounded-rands.html" rel="nofollow noreferrer">surveyed by M. O'Neill</a> are not optimal, since they rely on generating blocks of random bits at a time. See also my note on <a href="https://peteroupc.github.io/randomfunc.html#RNDINT_Random_Integers_in_0_N" rel="nofollow noreferrer">integer generating algorithms</a>.</p>
<p>The following is a JavaScript implementation of the Fast Dice Roller. Note that it uses rejection events and a loop to ensure it's unbiased. <code>nextBit()</code> is a method that produces an independent unbiased random bit (e.g., <code>Math.random()&lt;0.5 ? 1 : 0</code>, which isn't necessarily efficient in terms of random bits ultimately relied on in JavaScript).</p>
<pre><code>function randomInt(minInclusive, maxExclusive) {
 var maxInclusive = (maxExclusive - minInclusive) - 1
 var x = 1
 var y = 0
 while(true) {
    x = x * 2
    var randomBit = nextBit()
    y = y * 2 + randomBit
    if(x &gt; maxInclusive) {
      if (y &lt;= maxInclusive) { return y + minInclusive }
      // Rejection
      x = x - maxInclusive - 1
      y = y - maxInclusive - 1
    }
 }
}
</code></pre>
<h3>Reducing Bit Waste</h3>
<p>Recall that &quot;optimal&quot; integer generators, such as the Fast Dice Roller above, use on average at least <code>log2(n)</code> bits (the lower bound), or come within 2 bits of this lower bound on average. There are various techniques that can be used to bring an algorithm (even a less than optimal one) closer to this theoretical lower bound, including batching and randomness extraction. These are discussed in:</p>
<ul>
<li>The Fast Dice Roller paper itself, see section 3.1 (batching).</li>
<li>The paper &quot;<a href="https://arxiv.org/abs/1502.02539" rel="nofollow noreferrer">Random variate generation using only finitely many unbiased, independently and identically distributed random bits</a>&quot; by Devroye and Gravel, section 2.3 (randomness extraction).</li>
<li>The Math Forum page given above (recycling).</li>
</ul>
<p>The following are examples of &quot;batching&quot;: to generate four random digits from 0 through 9, simply generate a random integer in [0, 9999], and break the resulting number into digits. Generating eight random digits instead would involve the interval [0, 99999999].</p>
RNGCryptoServiceProvider - generate number in a range faster and retain distribution?
<p>I'm using the RNG crypto provider to generate numbers in a range the truly naive way:</p>
<pre><code>byte[] bytes = new byte[4];
int result = 0;
while(result &lt; min || result &gt; max)
{
   RNG.GetBytes(bytes);
   result = BitConverter.ToInt32(bytes);
}  
</code></pre>
<p>This is great when the range is wide enough such that there is a decent chance of getting a result, but earlier today I hit a scenario where the range is sufficiently small (within 10,000 numbers) that it can take an age.</p>
<p>So I've been trying to think of a better way that will achieve a decent distribution but will be faster.  But now I'm getting into deeper maths and statistics that I simply didn't do at school, or at least if I did I have forgotten it all!</p>
<p>My idea is:</p>
<ul>
<li>get the highest set bit positions of the min and max, e.g. for 4 it would be 3 and for 17 it would be 5</li>
<li>select a number of bytes from the prng that could contain at least the high bits, e.g.1 in this case for 8 bits</li>
<li>see if any of the upper bits in the allowed range (3-5) are set</li>
<li>if yes, turn that into a number up to and including the high bit</li>
<li>if that number is between min and max, return.</li>
<li>if any of the previous tests fail, start again.</li>
</ul>
<p>Like I say, this could be exceedingly naive, but I am sure it will return a match in a narrow range faster than the current implementation.  I'm not in front of a computer at the moment so can't test, will be doing that tomorrow morning UK time.</p>
<p>But of course speed isn't my only concern, otherwise I would just use <em>Random</em> (needs a couple of tick marks there to format correctly if someone would be kind enough - they're not on the Android keyboard!).</p>
<p>The biggest concern I have with the above approach is that I am always throwing away up to 7 bits that were generated by the prng, which seems bad.  I thought of ways to factor them in (e.g. a simple addition) but they seem terribly unscientific hacks!</p>
<p>I know about the mod trick, where you only have to generate one sequence, but I also know about its weaknesses.</p>
<p>Is this a dead end?  Ultimately if the best solution is going to be to stick with the current implementation I will, I just feel that there must be a better way!</p>