Pseudorandom Numbers In Your Head

I've recently looked up if there's a way to easily calculate random or pseudorandom numbers in your head, since the average person is demonstrably bad at generating truly or efficiently random numbers or sequences of coin flips or dice rolls.

One method that someone proposed is taking a seed - such as the current seconds measurement on a clock or watch - and multiplying the ones digit by six and adding the tens digit. Doing this iteratively will give us a pseudorandom string of digits in the ones column.

As an example of how this would work, my watch currently tells me as I'm writing this that it's 16:06:48. How can we get random numbers or digits from this?

\[48 \to 6 \times 8 + 4 = 52 \to 6 \times 2 + 5 = 17 \to 43 \to 16 \to 37 \to 45 \to 34 \dots\]

From this string, we can say that we have generated the pseudorandom string of digits below:

\[8 \to 6 \to 2 \to 7 \to 3 \to 6 \to 7 \to 5 \to 4 \dots \]

I want to test the diagnostics on this myself - see how close to a uniform distribution the digits distributed were and such - so I'm coding some figures below.

iterations = 1000

for seed in range(60):
    mod_seed = seed
    rand_string = []
    for i in range(iterations):
        ones = mod_seed % 10
        tens = mod_seed // 10

        mod_seed = 6 * ones + tens
        rand_string.append(mod_seed % 10)

    print(str(seed))
    print('_______')
    for j in range(10):
        print(str(j) + ' | ' + str(rand_string.count(j)))
    print()

It seems that the digits 1 through 8 are generated on average about equally as often as each other, while 0 and 9 are each consistently generated about 90% as often, a little less. Fascinating. This can be tested a bit more I think with other numbers and generation methods.

For example, this code I've modified to tally up total counts for the digits across all possible sequences generated.

iterations = 1000
tally_list = [0,0,0,0,0,0,0,0,0,0]

for seed in range(60):
    mod_seed = seed
    rand_string = []
    for i in range(iterations):
        ones = mod_seed % 10
        tens = mod_seed // 10

        mod_seed = 6 * ones + tens
        rand_string.append(mod_seed % 10)
    for j in range(10):
        tally_list[j] += rand_string.count(j)
print(tally_list)

As I suspected, the total sum is exactly the same across all digits, they each come up the same number of times. Perhaps there are other things I can do in order to make the pseudorandom strings a little more "equal" in their distributions for all numbers.

Merely adding 1 to each iterated number I think should do it - the 0 and 59 strings should be pushed out of their recurring strings.


iterations = 1000

for seed in range(60):
    mod_seed = seed
    rand_string = []
    for i in range(iterations):
        ones = mod_seed % 10
        tens = mod_seed // 10

        mod_seed = 6 * ones + tens + 1
        rand_string.append(mod_seed % 10)

    print(str(seed))
    print('_______')
    for j in range(10):
        print(str(j) + ' | ' + str(rand_string.count(j)))
    print()

Well there goes that theory. I screwed around a little with adding other constants, different multipliers, but none of them work great at making the probabilities uniform. There is strange number theory at play here, it would be very worthy of examination in the future to understand what is going on.

Here's an interesting question: can these pseudorandom sequences be effective dice rolls as well? To do this, we'll need to check on the dice rolls modulo 6 to see how they're distributed.

iterations = 1000

for seed in range(60):
    mod_seed = seed
    rand_string = []
    for i in range(iterations):
        ones = mod_seed % 10
        tens = mod_seed // 10

        mod_seed = 6 * ones + tens
        rand_string.append(mod_seed % 6 + 1)

    print(str(seed))
    print('_______')
    for j in range(6):
        print(str(j+1) + ' | ' + str(rand_string.count(j+1)))
    print()

Similar problem: the first and last digits still appear at about 90% as often as the others. Frustrating. But how frustrating? How about the sums of two rolled pseudorandom numbers? Will the difference be significant? Only one way to find out.

iterations = 100

for seed_1 in range(60):
    for seed_2 in range(60):
        mod_seed_1 = seed_1
        mod_seed_2 = seed_2
        rand_string = []
        for i in range(iterations):
            ones_1 = mod_seed_1 % 10
            tens_1 = mod_seed_1 // 10

            ones_2 = mod_seed_2 % 10
            tens_2 = mod_seed_2 // 10

            mod_seed_1 = 6 * ones_1 + tens_1
            mod_seed_2 = 6 * ones_2 + tens_2
            rand_string.append((mod_seed_1 % 6 + 1) + (mod_seed_2 % 6 + 1))

        print(str(seed_1) + ' ' + str(seed_2))
        print('_______')
        for j in range(11):
            print(str(j+2) + ' | ' + str(rand_string.count(j+2)/iterations))
        print()

I don't recommend running this code, I did once and the resulting text files was a little over 400 kb. However, the proportions are decently close to a standard two dice summed distribution, which is gratifying.

The pseudorandom numbers we can generate from a second count on a watch and repeated calculations can be used for several other useful calculations. We can already generate the digits 0-9 pretty close to uniform. The results of rolling two dice can be pretty closely simulated by taking two such second counts, or two pseudorandom numbers in sequence (mod 6) and adding them together. Flipping a coin is easy - generate a few pseudorandom digits, evens are heads and odds are tails. There's certainly other ways that we could use these random numbers to get useful random variables.


Back to Math Hub
Back to homepage