【问题标题】:How does ThreadLocalRandom.nextLong(long n) work?ThreadLocalRandom.nextLong(long n) 是如何工作的?
【发布时间】:2013-11-14 04:59:17
【问题描述】:

这是code我不明白:

// Divide n by two until small enough for nextInt. On each
// iteration (at most 31 of them but usually much less),

什么?用我琐碎的simulation 随机选择n 我得到了32 迭代,31 是平均值。

// randomly choose both whether to include high bit in result
// (offset) and whether to continue with the lower vs upper
// half (which makes a difference only if odd).

这是有道理的。

long offset = 0;
while (n >= Integer.MAX_VALUE) {
    int bits = next(2);

为两个决定获取两个位,这是有道理的。

    long half = n >>> 1;
    long nextn = ((bits & 2) == 0) ? half : n - half;

这里,nn/2.0 向上或向下舍入,很好。

    if ((bits & 1) == 0) offset += n - nextn;
    n = nextn;

我迷路了。

}
return offset + nextInt((int) n);

我可以看到它生成了一个适当大小的数字,但它看起来很复杂而且速度很慢,我绝对看不出为什么结果应该是均匀分布的。1


1它不能真正均匀分布,因为状态只有 48 位,所以它可以生成不超过 2**48 个不同的数字。绝大多数longs 无法生成,但显示它的实验可能需要数年时间。

【问题讨论】:

    标签: java random


    【解决方案1】:

    我认为你有点误会了......让我试着用我看到的方式来描述算法:

    首先,假设nextInt(big)(nextInt,而不是nextLong)能够正确地生成0(包括)和big(不包括)之间分布良好的值范围。

    nextInt(int) 函数用作nextLong(long) 的一部分

    因此,该算法通过循环工作,直到该值小于 Integer.MAX_INT,此时它使用nextInt(int)。更有趣的是它在这之前做了什么……

    从数学上讲,如果我们取一个数字的一​​半,减去它的一半,然后减去一半的一半,然后再减去一半的一半,等等,我们这样做的次数足够多,它就会趋于零。类似地,如果你取一个数字的一​​半,并将它加到一半的一半,等等,它会趋向于原始数字。

    算法在这里所做的是它需要一半的数字。通过进行整数除法,如果数字是奇数,则有一个“大”的一半和一个“小”的一半。算法“随机”选择其中一半(大或小)。

    然后,它随机选择添加那一半,或者不添加那一半到输出中。

    它不断将数字减半并(可能)添加一半,直到一半小于 Integer.MAX_INT。此时,它只计算 nextInt(half) 值并将其添加到结果中。

    假设最初的 long 限制远大于Integer.MAX_VALUE,那么最终结果将获得 nextInt(int) 的所有好处,因为它具有至少 32 位状态以及 2 位状态的大 int 值对于Integer.MAX_VALUE 以上的所有高位。

    原始限制越大(越接近 Long.MAX_VALUE),循环迭代的次数就越多。在最坏的情况下,它将经过 32 次,但对于较小的限制,它将经过更少的次数。在最坏的情况下,对于非常大的限制,您将获得用于循环的 64 位随机性,然后nextInt(half) 也需要任何东西。


    编辑:WalkThrough 添加 - 计算结果的数量更难,但是,从 0Long.MAX_VALUE - 1 的所有 long 值都是可能的结果。使用nextLong(0x4000000000000000) 的“证明”是一个很好的例子,因为所有的减半过程都是偶数,并且设置了第 63 位。

    因为设置了第 63 位(可用的最高有效位,因为 bit64 会使数字变为负数,这是非法的)这意味着我们将在值 half 将是 0x0000000004000000....)。因为减半和位移是同一个过程,所以它认为有尽可能多的一半要做,就像最高位集和第 31 位之间的差一样。63 - 31 是 32,所以我们将事情减半 32 次,因此我们做了 32在while循环中循环。 0x4000000000000000 的初始起始值意味着当我们将值减半时,一半中只会设置一个位,并且它将“走”该值 - 每次循环都向右移动 1。

    因为我仔细选择了初始值,所以很明显,在while循环中,逻辑本质上是决定是否设置每个位。它取输入值的一半(即 0x2000000000000000)并决定是否将其添加到结果中。让我们假设我们所有的循环都决定将一半添加到偏移量,在这种情况下,我们从偏移量 0x0000000000000000 开始,然后每次通过循环我们添加一半,这意味着每次我们添加:

    0x2000000000000000
    0x1000000000000000
    0x0800000000000000
    0x0400000000000000
    .......
    0x0000000100000000
    0x0000000080000000
    0x0000000040000000  (this is added because it is the last 'half' calculated)
    

    此时,我们的循环已经运行了 32 次,它已经“选择”将值相加 32 次,因此值中至少有 32 个状态(如果计算大/小一半决策,则为 64)。实际偏移量现在为0x3fffffffc0000000(从 62 到 31 的所有位都已设置)。

    然后,我们调用 nextInt(0x40000000),幸运的是,它使用 31 位状态生成结果 0x3fffffff。我们将此值添加到我们的偏移量并得到结果:

    0x3fffffffffffffff
    

    通过nextInt(0x40000000) 结果的“完美”分布,我们将完美地覆盖0x7fffffffc00000000x3fffffffffffffff 的值,没有间隙。在我们的while循环中具有完美的随机性,我们的高位将是0x00000000000000000x3fffffffc0000000的完美分布结合起来,从0到我们的极限(不包括)0x4000000000000000的完全覆盖

    使用来自高位的 32 位状态,以及来自nextInt(0x40000000) 的(假设的)最少 31 位状态,我们有 63 位状态(如果算上大/小一半的决定,则更多),并且完全覆盖。

    【讨论】:

    • 最后一段似乎反对我关于均匀分布的主张。您说的是“64 位随机性”,但只有 48 位状态。无论您调用哪种确定性方法,您都无法获得比 2**48 更多的结果。
    • 添加了一个演示如何为大型输入提供所有值
    • 我明白了,但是你假设所有的决定都是独立的,这对于像 SecureRandom 这样的子类是正确的,但对于 Random 本身是不可能的。从已知状态开始的任何确定性计算都必须返回相同的结果——这是根据定义。从 1000 个状态之一开始最多可以返回 1000 个不同的状态,对吗?从2**48 州开始......你看到了吗?另请参阅this "Inverse function of Random function" question
    • 我接受你的回答,它很好而且非常详细,但如果你能以某种方式合并我的最后评论,我会很高兴。
    • 您是否还在担心是否可以生成所有 0..Long.MAX_VALUE 范围内的 2^63 值,或者您对答案的那部分感到满意​​?如果您关心的是 Random 类中的 48 位种子,那么您应该记住,有 2^48 种不同的可能伪随机序列......并且 48 位种子不会影响统计分布、范围、或任何一个序列的覆盖范围。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-02-14
    • 1970-01-01
    • 2015-10-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多