When using class java.util.Random, how can one get the value obtained from calling the method nextInt() N times, but in a much more efficient way (in O(1) specifically)?
For example, if I construct a Random object with a particular seed value, and I want to get the 100,000th "nextInt() value" (that is, the value obtained after calling the method nextInt() 100,000 times) in a fast way, could I do it?
Assume, for simplicity, version 1.7.06 of the JDK, since it may be required to know the exact values of some private fields in class Random. And speaking of, I found the following fields to be relevant in the calculation of a random value:
private static final long multiplier = 0x5DEECE66DL;
private static final long addend = 0xBL;
private static final long mask = (1L << 48) - 1;
After exploring a bit about randomness, I found that random values are obtained using a Linear congruential generator. The actual method that executes the algorithm is method next(int):
protected int next(int bits) {
long oldseed, nextseed;
AtomicLong seed = this.seed;
do {
oldseed = seed.get();
nextseed = (oldseed * multiplier + addend) & mask;
} while (!seed.compareAndSet(oldseed, nextseed));
return (int)(nextseed >>> (48 - bits));
}
The relevant line for the algorithm is the one that obtains the next seed value:
nextseed = (oldseed * multiplier + addend) & mask;
So, to be more specific, is there a way that I can generalize this formula to obtain the "nth nextseed" value? I'm assuming here that after having that, I can then simply obtain the nth int value by letting the variable "bits" be 32 (the method nextInt() simply calls next(32) and returns the result).
Thanks in advance
PS: Perhaps this is a question more suitable for mathexchange?
I've accepted the answer from Daniel Fischer as it is correct and gives the general solution. Using Daniel's answer, here is a concrete example with java code that shows a basic implementation of the formula (I used class BigInteger extensively so it may not be optimal, but I confirmed a significant speedup over the rudimentary way of actually calling the method nextInt() N times):
NOTE: Notice the method initialScramble(long), which is used to get the first seed value. This is the behavior of class Random when initializing an instance with a specific seed.
You can do it in
O(log N)
time. Starting withs(0)
, if we ignore the modulus (248) for the moment, we can see (usingm
anda
as shorthand formultiplier
andaddend
) thatNow,
m^N (mod 2^48)
can easily be computed inO(log N)
steps by modular exponentiation by repeated squaring.The other part is a bit more complicated. Ignoring the modulus again for the moment, the geometric sum is
What makes computing this modulo
2^48
a bit nontrivial is thatm - 1
is not coprime to the modulus. However, sincethe greatest common divisor of
m-1
and the modulus is 4, and(m-1)/4
has a modular inverseinv
modulo2^48
. LetThen
So
M ≡ m^N (mod 2^50)
inv
to obtain