Try to think of 1010 completely random integers between 11 and 1010. Each number should have no relationship at all to the others. It’s not that easy right?

Human brains follow directions very well. However, this makes them very bad at being completely random, or even random at all. Similarly, computers struggle with being truly random. Yet, a \verb|random| function is ubiquitous across programming languages, calculators, and more. Where do these devices get their capabilities?

It turns out that these random functions are just very good at pretending to be random. Many such functions follow a known formula that just happens to be hard to guess by hand. In this article, we will examine pseudorandom functions, particularly the one in Java.

First, for the skeptics. Run the following Java code as many times as you want, and verify for yourself that the result is always the same.

import java.util.Random; public class JavaIsNotRandom { public static void main(String[] args) { randomNumbers(6); } public static void randomNumbers(long seed) { Random r = new Random(seed); for (int i = 0; i < 100; i++) { System.out.println(r.nextInt(32)); } } } You can find the same pattern by changing any of the numbers (66⁠, 00⁠, 100100⁠, 3232). While the output differs between different parameters, the output is the same no matter how many times you run your code with the same set of parameters. Very suspicious for a “random” function.

Java employs what is known as a Linear Congruential Generator (LCG). This is a fancy way to describe the sequence X0⁠,X1⁠,X2X_0, X_1, X_2 \dots where XnaXn1+c(modm)X_n \equiv aX_{n-1} + c for constant integers aa⁠, cc⁠, mm. When you create a variable in the Random class, you (or Java) provide X0X_0⁠, a seed. Each successive time you use the same random variable, Java just takes the next term in the sequence and modifies it in a known way based on what you query for (e.g. truncate if you set a upper bound, add a decimal point, etc.). For instance, Java in essence returns the five bits from each of X1⁠,X2⁠,X3X100X_1, X_2, X_3 \dots X_{100} for X0=6X_0 = 6 in the code above.

The principle behind an LCG is that the period of XX will be mm for good choices of of aa⁠, cc⁠, and mm. For instance, if c=0c = 0⁠, mm is prime, and aa and mm are relatively prime, then Xm1am1X0X0(modm)X_{m-1}\equiv a^{m-1} X_0 \equiv X_0 by Fermat’s Little Theorem. If one makes mm really big, e.g. 26112^{61}-1⁠, then the period becomes large enough where it is not immediately obvious something deterministic is happening.

In fact, we don’t even need to be so picky about the values of the constants. The Hull-Dobell theorem states that the period of XX will be mm so long as mm and cc are relatively prime, a1a-1 is divisible by all factors of mm⁠, and a1a-1 is divisible by 44 if mm is divisible by 44. This allows developers to set mm to powers of 22⁠, which makes modular arithmetic really simple computationally.

Specifically, Java has m=248m = 2^{48}⁠, c=11c = 11⁠, and a=25214903917a = 25214903917. Other languages, such as certain random functions in C++, utilize different hyperparameters. Java also tries to make its random function even more random by letting the default seed be the current Unix time, which constantly varies. In other words, don’t set a seed for more random results.

LCGs have a number of advantages, including efficiency and simplicity. However, it is very easy to reverse an LCG, and LCGs also fail many statistical tests. Other pseudorandom functions, such as shift-register generators, which use bit-shift operations instead of addition and multiplications, are more random. For instance, Python uses a Mersenne twister, a shift-generator with a very large modulus. One can also pair multiple pseudorandom functions, or apply output mixing functions, to make the resuts even more random. Nevertheless, only certain natural phenomenon, such as atmospheric noise and nuclear decay are considered truly random.

All of this is not to say that pseudorandom number generators are bad. Randomness is important in applications ranging from clinical trials to gambling, and pseudomrandom number generators have served us well so far.