调整XORShift发生器以返回最大值

时间:2012-11-03 20:52:57

标签: java performance random

我需要在最大值内生成随机整数。由于性能至关重要,我决定使用XORShift生成器而不是Java的Random类。

long seed = System.nanoTime();
seed ^= (seed << 21);
seed ^= (seed >>> 35);
seed ^= (seed << 4);

这个实现 (source) 给了我一个长整数,但我真正想要的是一个介于0和最大值之间的整数。

public int random(int max){ /*...*/}

实施此方法的最有效方法是什么?

2 个答案:

答案 0 :(得分:7)

我对您的代码感兴趣并想出了这个:

public class XORShiftRandom {

private long last;
private long inc;

public XORShiftRandom() {
    this(System.currentTimeMillis());
}

public XORShiftRandom(long seed) {
    this.last = seed | 1;
    inc = seed;
}

public int nextInt(int max) {
    last ^= (last << 21);
    last ^= (last >>> 35);
    last ^= (last << 4);
    inc += 123456789123456789L;
    int out = (int) ((last+inc) % max);     
    return (out < 0) ? -out : out;
}

}

我做了一个简单的测试,它与java.util.Random

一样快四倍

如果您对它的工作方式感兴趣,可以阅读paper

Disclamer:

  

上面的代码仅用于研究,而不是用于研究   替换库存Random或SecureRandom。

答案 1 :(得分:2)

幼苗

这里有很多问题。如果你不止一次使用nanoTime,你肯定做错了,因为nanoTime很慢(几百纳秒)。此外,这样做可能会导致质量下降。

因此,我们假设您只为种子生成一次。

均匀

如果关心一致性,那么至少有两个问题:

Xorshift

它永远不会产生零(除非你不熟悉播种,然后零就是你得到的)。

这很容易通过像

这样简单的东西来解决
private long nextLong() {
    x ^= x << 21;
    x ^= x >>> 35;
    x ^= x << 4;
    y += 123456789123456789L;
    return x + y;
}

使用的常量非常随意,除非它必须是奇数。为了获得最佳结果,它应该很大(以便所有位经常更改),它应该有很多位转换(在二进制表示中出现1001)并且它不应该太常规(0x55...55很糟糕)。

但是,使用x!=0和任何奇数常量,均匀性得到保证,生成器的周期为2**64 * (2*64-1)

我建议播种

seed = System.nanoTime();
x = seed | 1;
y = seed;

nextInt(int limit)

由于我在comment中提到的原因,接受的答案提供了非均匀分布的值。做得对,有点复杂,你可以从Random#nextInt复制代码,或者你可以尝试这样的事情(未经测试):

public int nextInt(int limit) {
    checkArgument(limit > 0);
    int mask = -1 >>> Integer.numberOfLeadingZeros(limit);
    while (true) {
        int result = (int) nextLong() & mask;
        if (result < limit) return result;
    }
}

在上面,mask看起来像二进制文件0...01...1,其中最高的一个对应limit中的最高一个。使用它,可以生成0..mask范围内的均匀分布数(由于mask+1是2的幂),因此一致性很容易。条件拒绝不低于limit的数字。如limit > mask/2,这种情况发生的概率低于50%,因此预期的迭代次数低于2。

建议

这很有趣,但测试很难,我建议改为使用ThreadLocalRandom,除非你需要重复性。