urandom 0.2.2

Produce and consume randomness, to convert them to useful types and distributions, and some randomness-related algorithms.
Documentation
fn main() {
	// Let's say our random number generator's range is `0..10`.
	// Let's say we want a random number in the interval `[0, 4)`.
	// To demonstrate the problem let's evaluate all generator outputs and see what number we get.

	println!("Random values in `[0, 4)` with bias:");
	for rng in 0..10 {
		// Using multiplication instead of remainder for performance reasons.
		// In practice the division is a power of two which is very efficient to work with.
		let value = rng * 4 / 10;

		print!("{value}  ");
	}
	println!();

	// The result prints:
	// 0  0  0  1  1  2  2  2  3  3

	// Clearly showing that `0` and `2` are more likely to appear which isn't very uniform.
	// To fix this let's reject some randomness produced by the rng.

	println!("\nRandom values in `[0, 4)` without bias:");
	for rng in 0..10 {
		let value = rng * 4 / 10;
		// Here's the magic, for every value reject some which cause a biased result.
		// Again in practice the remainder is a power of two which is very efficient to work with.
		let reject = rng * 4 % 10;
		if reject >= 2 {
			print!("{value}  ");
		}
	}
	println!();

	// The result prints:
	// 0  0  1  1  2  2  3  3

	// Now the distribution has been fixed, all the values in the requested interval are equally likely to appear.

	// Above the magic constant `2` was used to reject randomness.
	// This is very simple to calculate:
	// Observe that the range of the interval is `4` which fits in the underlying rng's range of `10` twice, with 2 to spare.
	// It's these two samples that we must reject as they are impossible to uniformly distribute over the input range.

	// This value can be calculated as `reject = (MAX - range) % range`.
	// For our example this is `reject = (10 - 4) % 4 = 2`.
	// In practice MAX will be `0` (does not fit in the integer) but wrapping_sub can be used.

	// This costs a potentially expensive integer division.
	// This can be worked around by using any other multiple of the range that is cheaper to calculate.
	// For example simply shift the range left until the most significant bit is 1.
	// It comes at the cost of potentially more rejections before a sample is accepted, but never more than 50%.
	// This can be avoided if the input range is known at compiletime where it can be constant folded.

	// Next is the calculation of the lookup table used for small ranges for u32 and u64 randomness ranges.
	if std::env::args().count() > 1 {
		println!("\nLookup table for u32 rejection sampling:");
		for range in 1..65 {
			let reject = u32::wrapping_sub(0, range) % range;
			print!("{reject}, ");
			if range % 8 == 0 {
				println!();
			}
		}
		println!("\nLookup table for u64 rejection sampling:");
		for range in 1..65 {
			let reject = u64::wrapping_sub(0, range) % range;
			print!("{reject}, ");
			if range % 8 == 0 {
				println!();
			}
		}
	}
}