【问题标题】:Adjusting XORShift generator to return a number within a maximum调整 XORShift 生成器以返回最大范围内的数字
【发布时间】:2012-10-24 04:25:22
【问题描述】:

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

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

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

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

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

【问题讨论】:

  • 你不能把long取最大值吗?
  • @SajitKunnumkal 这会对某些整数产生(轻微)偏差(当然,如果 max 足够小,则可以忽略不计)。
  • 如果性能很关键,你应该避免nanoTime()。
  • 如果性能很关键,您可能应该改用简单的 LCG。如果 max 是恒定的并且提前知道,您甚至可以构建一个 ad-hoc LCG,它将直接生成 [0, max) 范围内的数字而没有偏差。
  • 使用 XORShift 时不应该考虑性能;您正在使用它的事实意味着它适用于非关键应用程序,这意味着 System.nanoTime() 非常适合。

标签: java performance random


【解决方案1】:

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

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:

免责声明:

以上代码仅用于研究,不作为 替换为库存 Random 或 SecureRandom。

【讨论】:

  • 21、35、4的移位值有什么解释吗?也就是说,您是否使用其他值来测试速度和/或随机性?
  • 这些值来自 OP,我没有玩它们。也许问他这个问题。
  • 快速讨论为什么使用这些值可以在这里找到:javamex.com/tutorials/random_numbers/…
  • 请注意(与java.util.Random 不同)您的结果并非完全均匀分布。它们不能是 2**32(整数的数量)不能被 max 整除,除了 2 的幂。对于像3e9 这样非常大的max,比例是1:2,对于小的值,它会好得多。
  • @maaartinus 我认为这是非常重要的一点,不应低估。我将要求解释为要找到一种性能高效的方法,将结果缩放到所需的范围,而不会损害原始算法的均匀分布特性。取模数并不能满足这一点。
【解决方案2】:

播种

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

所以让我们假设,您只为生成器播种一次。

均匀度

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

Xorshift

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

这很容易通过简单的方法解决

private long nextLong() {
    x ^= x << 21;
    x ^= x >>> 35;
    x ^= x << 4;
    y += 123456789123456789L;
    return x + y;
}

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

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

我建议像播种

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

nextInt(int 限制)

由于我在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 范围内的均匀分布的数字被生成(unifomrnity 很容易,因为mask+1 是 2 的幂)。条件拒绝不低于limit 的数字。如limit &gt; mask/2,这种情况发生的概率低于 50%,因此预期的迭代次数低于 2。

推荐

玩弄这个很有趣,但测试它很困难,我建议改用ThreadLocalRandom,除非您需要可重复性。

【讨论】:

  • 不错。感谢您抽出宝贵时间回复,并提供可靠方法的基础!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-07-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-14
  • 2015-03-21
相关资源
最近更新 更多