关于Java:THead LoopRealNo.nExlong(long n)是如何工作的?

How does ThreadLocalRandom.nextLong(long n) work?

这是我不理解的代码:

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

什么?通过随机选择的n的简单模拟,我得到了32迭代,31是平均值。

1
2
3
// 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).

这是有道理的。

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

为这两个决定取两个比特,这是有道理的。

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

这里,nn/2.0向上或向下取整,很好。

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

我迷路了。

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

我可以看到它生成了一个适当大小的数字,但它看起来相当复杂,速度相当慢,我肯定不明白为什么结果应该是均匀分布的。1

它不能真正均匀分布,因为状态只有48位,所以它可以生成不超过2*48个不同的数字。绝大多数的long是不能产生的,但一项实验表明,这可能需要数年时间。


我觉得你有点错了……让我试着用我看到的方式来描述算法:好的。

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

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

因此,该算法通过循环工作,直到值小于integer.max_int,此时它使用nextInt(int)。更有趣的是它在那之前所做的…好的。

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

这里的算法只需要一半的数字。通过做整数除法,如果数字是奇数,那么有一个"大"一半和一个"小"一半。算法"随机"选择其中的一部分(大的或小的)。好的。

然后,它随机选择添加这一半,或者不将这一半添加到输出。好的。

它不断地将数字减半,并(可能)添加一半,直到一半小于integer.max int。此时,它只需计算nextInt(half)值并将其添加到结果中。好的。

假设初始长限比Integer.MAX_VALUE大得多,那么对于至少32位状态的大int值,最终结果将获得nextint(int)的所有好处,对于Integer.MAX_VALUE以上的所有高位,最终结果将获得2位状态。好的。

原始限制越大(越接近long.max_值),循环迭代的次数越多。在最坏的情况下,这将是32次,但对于较小的限制,它将经历较少的次数。在最坏的情况下,对于非常大的限制,您将得到用于循环的64位随机性,然后得到nextInt(half)所需的任何内容。好的。

编辑:Walkthrough增加了计算结果数量的难度,但是,从0Long.MAX_VALUE - 1的所有长值都是可能的结果。作为"证据",使用nextLong(0x4000000000000000)是一个很好的例子,因为所有的减半过程将是均匀的,并且它有位63集。好的。

因为设置了位63(因为位64会使数字变为负数,这是非法的,所以最有效的位),这意味着在值<=integer.max_value(即0x000000007fffffff-之前,我们将值减半32倍,当我们到达该位置时,我们的half将是0x0000000004000000…。因为减半和位移动是相同的过程,所以它认为有尽可能多的减半要做的是最高位集和位31之间的差异。63-31是32,所以我们将事物减半32倍,这样我们在while循环中做32个循环。0x4000000000000000的初始起始值意味着,当我们将该值减半时,一半中只设置了一个位,它将沿着值"走",每次循环将1向右移动。好的。

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

1
2
3
4
5
6
7
8
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),幸运的是,它会产生结果0x3ffffff,使用31位状态到达那里。我们将此值添加到偏移量中,得到结果:好的。

1
0x3fffffffffffffff

如果nextInt(0x40000000)结果的"完美"分布,我们就可以完全覆盖0x7fffffffc00000000x3fffffffffffffff之间的值,而没有间隙。由于while循环的完全随机性,我们的高位将是0x00000000000000000x3fffffffc0000000的完美分布组合,从0到我们的限制(不包括0x4000000000000000的完全覆盖。好的。

从高位得到32位的状态,从nextInt(0x40000000)得到(假定的)最小31位的状态,我们有63位的状态(如果计算大/小一半的决定,则更多)和完全覆盖。好的。好啊。