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

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

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

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

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

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

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

 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四倍

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

Disclamer:

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

播种

这里有很多问题。 如果你不止一次使用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)

由于我在评论中提到的原因,接受的答案提供了非均匀分布的值。 做得对,有点复杂,你可以从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 ,除非你需要重复性。