Task

(Given an equal-probability generator of one of the integers 1 to 5
as dice5),   create dice7 that generates a pseudo-random integer from
1 to 7 in equal probability using only dice5 as a source of random
numbers,   and check the distribution for at least one million calls using the function created in   Simple Random Distribution Checker.


Implementation suggestion:
dice7 might call dice5 twice, re-call if four of the 25
combinations are given, otherwise split the other 21 combinations
into 7 groups of three, and return the group index from the rolls.

(Task adapted from an answer here)

import std.random;
import verify_distribution_uniformity_naive: distCheck;
 
/// Generates a random number in [1, 5].
int dice5() /*pure nothrow*/ @safe {
    return uniform(1, 6);
}
 
/// Naive, generates a random number in [1, 7] using dice5.
int fiveToSevenNaive() /*pure nothrow*/ @safe {
    immutable int r = dice5() + dice5() * 5 - 6;
    return (r < 21) ? (r % 7) + 1 : fiveToSevenNaive();
}
 
/**
Generates a random number in [1, 7] using dice5,
minimizing calls to dice5.
*/
int fiveToSevenSmart() @safe {
    static int rem = 0, max = 1;
 
    while (rem / 7 == max / 7) {
        while (max < 7) {
            immutable int rand5 = dice5() - 1;
            max *= 5;
            rem = 5 * rem + rand5;
        }
 
        immutable int groups = max / 7;
        if (rem >= 7 * groups) {
            rem -= 7 * groups;
            max -= 7 * groups;
        }
    }
 
    immutable int result = rem % 7;
    rem /= 7;
    max /= 7;
    return result + 1;
}
 
void main() /*@safe*/ {
    enum int N = 400_000;
    distCheck(&dice5, N, 1);
    distCheck(&fiveToSevenNaive, N, 1);
    distCheck(&fiveToSevenSmart, N, 1);
}