How to obtain the nth random “nextInt” value?
当使用java.util.random类时,如何获得通过多次调用nextint()方法获得的值,但要以更有效的方式(特别是在o(1)中)?
例如,如果我用一个特定的种子值构造一个随机对象,并且我想要以一种快速的方式得到100000个"nextint()值"(也就是说,在调用方法nextint()100000次后获得的值),我可以这样做吗?
为了简单起见,假设JDK的1.7.06版本,因为可能需要知道类随机中某些私有字段的确切值。说到这里,我发现在计算一个随机值时,下列字段是相关的:
1 2 3 | private static final long multiplier = 0x5DEECE66DL; private static final long addend = 0xBL; private static final long mask = (1L << 48) - 1; |
在研究了一些随机性之后,我发现使用线性同余生成器可以获得随机值。执行算法的实际方法是method next(int):
1 2 3 4 5 6 7 8 9 | 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)); } |
算法的相关行是获得下一个种子值的行:
1 | nextseed = (oldseed * multiplier + addend) & mask; |
所以,更具体地说,有没有一种方法,我可以推广这个公式,得到"nth nextseed"值?在这里我假设在拥有了它之后,我可以简单地通过让变量"bits"为32来获得nth int值(方法next int()只调用next(32)并返回结果)。
提前谢谢
附:也许这是一个更适合数学交流的问题?
你可以在
1 2 3 4 5 | s(1) = s(0) * m + a s(2) = s(1) * m + a = s(0) * m2 + (m + 1) * a s(3) = s(2) * m + a = s(0) * m3 + (m2 + m + 1) * a ... s(N) = s(0) * m^N + (m^(N-1) + ... + m + 1) * a |
现在,
另一部分有点复杂。此时再次忽略模量,几何和为
1 | (m^N - 1) / (m - 1) |
计算这个模
1 | m = 0x5DEECE66DL |
1 | c = (m^N - 1) (mod 4*2^48) |
然后
1 | (c / 4) * inv ≡ (m^N - 1) / (m - 1) (mod 2^48) |
所以
- 计算
M ≡ m^N (mod 2^50) 。 - 计算
inv 。
获得
1 | s(N) ≡ s(0)*M + ((M - 1)/4)*inv*a (mod 2^48) |
我接受了丹尼尔·费舍尔的回答,因为它是正确的,并给出了一般的解决方案。使用丹尼尔的答案,这里有一个Java代码的具体示例,它显示了公式的基本实现(我广泛使用类BigTigple,因此它可能不是最优的,但是我确认了在实际调用方法NExtTin()n次的基本方法上的显著加速):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | import java.math.BigInteger; import java.util.Random; public class RandomNthNextInt { // copied from java.util.Random ========================= private static final long multiplier = 0x5DEECE66DL; private static final long addend = 0xBL; private static final long mask = (1L << 48) - 1; private static long initialScramble(long seed) { return (seed ^ multiplier) & mask; } private static int getNextInt(long nextSeed) { return (int)(nextSeed >>> (48 - 32)); } // ====================================================== private static final BigInteger mod = BigInteger.valueOf(mask + 1L); private static final BigInteger inv = BigInteger.valueOf((multiplier - 1L) / 4L).modInverse(mod); /** * Returns the value obtained after calling the method {@link Random#nextInt()} {@code n} times from a * {@link Random} object initialized with the {@code seed} value. * <p> * This method does not actually create any {@code Random} instance, instead it applies a direct formula which * calculates the expected value in a more efficient way (close to O(log N)). * * @param seed * The initial seed value of the supposed {@code Random} object * @param n * The index (starting at 1) of the"nextInt() value" * @return the nth"nextInt() value" of a {@code Random} object initialized with the given seed value * @throws IllegalArgumentException * If {@code n} is not positive */ public static long getNthNextInt(long seed, long n) { if (n < 1L) { throw new IllegalArgumentException("n must be positive"); } final BigInteger seedZero = BigInteger.valueOf(initialScramble(seed)); final BigInteger nthSeed = calculateNthSeed(seedZero, n); return getNextInt(nthSeed.longValue()); } private static BigInteger calculateNthSeed(BigInteger seed0, long n) { final BigInteger largeM = calculateLargeM(n); final BigInteger largeMmin1div4 = largeM.subtract(BigInteger.ONE).divide(BigInteger.valueOf(4L)); return seed0.multiply(largeM).add(largeMmin1div4.multiply(inv).multiply(BigInteger.valueOf(addend))).mod(mod); } private static BigInteger calculateLargeM(long n) { return BigInteger.valueOf(multiplier).modPow(BigInteger.valueOf(n), BigInteger.valueOf(1L << 50)); } // =========================== Testing stuff ====================================== public static void main(String[] args) { final long n = 100000L; // change this to test other values final long seed = 1L; // change this to test other values System.out.println(n +"th nextInt (formula) =" + getNthNextInt(seed, n)); System.out.println(n +"th nextInt (slow) =" + getNthNextIntSlow(seed, n)); } private static int getNthNextIntSlow(long seed, long n) { if (n < 1L) { throw new IllegalArgumentException("n must be positive"); } final Random rand = new Random(seed); for (long eL = 0; eL < (n - 1); eL++) { rand.nextInt(); } return rand.nextInt(); } } |
注意:注意方法的缩写cramble(long),它用于获取第一个种子值。这是使用特定种子初始化实例时类random的行为。