【问题标题】:How to obtain the nth random "nextInt" value?如何获得第 n 个随机“nextInt”值?
【发布时间】:2013-01-31 00:00:58
【问题描述】:

在使用 java.util.Random 类时,如何才能以更有效的方式(特别是在 O(1) 中)获得 N 次调用 nextInt() 方法获得的值?

例如,如果我构造一个带有特定种子值的 Random 对象,我想在一个快速的方法,我能做到吗?

为简单起见,假设 JDK 版本为 1.7.06,因为可能需要知道 Random 类中某些私有字段的确切值。说起来,我发现以下字段与随机值的计算相关:

private static final long multiplier = 0x5DEECE66DL;
private static final long addend = 0xBL;
private static final long mask = (1L << 48) - 1;

在探索了一下随机性之后,我发现随机值是使用线性同余生成器获得的。执行算法的实际方法是方法 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));
}

算法的相关行是获得下一个种子值的行:

nextseed = (oldseed * multiplier + addend) & mask;

那么,更具体地说,有没有一种方法可以推广这个公式来获得“nth nextseed”值?我在这里假设,在有了它之后,我可以通过让变量“bits”为 32 来简单地获得第 n 个 int 值(方法 nextInt() 只需调用 next(32) 并返回结果)。

提前致谢

PS:也许这是一个更适合mathexchange的问题?

【问题讨论】:

    标签: java random


    【解决方案1】:

    您可以在O(log N) 时间完成。从s(0) 开始,如果我们暂时忽略模数(248),我们可以看到(使用ma 作为multiplieraddend 的简写)

    s(1) = s(0) * m + a
    s(2) = s(1) * m + a = s(0) * m² + (m + 1) * a
    s(3) = s(2) * m + a = s(0) * m³ + (m² + m + 1) * a
    ...
    s(N) = s(0) * m^N + (m^(N-1) + ... + m + 1) * a
    

    现在,m^N (mod 2^48) 可以通过重复平方的模幂以 O(log N) 步骤轻松计算。

    另一部分有点复杂。暂时再忽略模数,几何和为

    (m^N - 1) / (m - 1)
    

    是什么让计算这个模 2^48 有点不平凡是因为 m - 1 不是模的互质数。然而,由于

    m = 0x5DEECE66DL
    

    m-1 的最大公约数,模为 4,(m-1)/4 有模逆 inv2^48。让

    c = (m^N - 1) (mod 4*2^48)
    

    然后

    (c / 4) * inv ≡ (m^N - 1) / (m - 1) (mod 2^48)
    

    所以

    • 计算M ≡ m^N (mod 2^50)
    • 计算inv

    获得

    s(N) ≡ s(0)*M + ((M - 1)/4)*inv*a (mod 2^48)
    

    【讨论】:

    • 感谢您的帮助。有关 java 中的特定实现,请参阅我的答案。
    • 好。对我来说太晚了,我无法真正实现它;)请注意,因为模数是 2 的幂,并且 Java 保证 long 算术的环绕,即算术模 2^64,没有实际的需要使用BigInteger,如果性能很关键,使用普通的long 可能会更快。
    • 是的,我想可能有一些捷径。不过,最终导致我首先使用 BigInteger 的原因是 modPow(power, mod) 和 modInverse(mod) 等函数的即时可用性。回想起来,看代码,我仍然可以将 BigInteger 用于 modInverse 函数,因为它只计算一次。
    • 是的,BigInteger 减轻了您编写自己的函数的负担。你根本不需要打电话给modInverse,你可以计算一次并得到它private static final long inverse = 0x11018AFE8493L;。如果你改变了乘数,你只需要计算它,然后你还需要调整 2 除以multiplier-1 的幂。
    【解决方案2】:

    我已接受 Daniel Fischer 的回答,因为它是正确的并给出了一般解决方案。使用 Daniel 的回答,这里是一个带有 java 代码的具体示例,它显示了公式的基本实现(我广泛使用了 BigInteger 类,因此它可能不是最佳的,但我证实了实际调用方法 nextInt( ) N 次):

    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();
        }
    }
    

    注意:注意方法 initialScramble(long),它用于获取第一个种子值。这是使用特定种子初始化实例时类 Random 的行为。

    【讨论】:

      猜你喜欢
      • 2019-10-11
      • 2020-09-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-07-02
      • 2018-09-14
      • 2016-08-05
      相关资源
      最近更新 更多