随机实现
对于这个答案,您必须知道的最重要的事情是实现:Random.nextGaussian
synchronized public double nextGaussian() {
// See Knuth, ACP, Section 3.4.1 Algorithm C.
if (haveNextNextGaussian) {
haveNextNextGaussian = false;
return nextNextGaussian;
} else {
double v1, v2, s;
do {
v1 = 2 * nextDouble() - 1; // between -1 and 1
v2 = 2 * nextDouble() - 1; // between -1 and 1
s = v1 * v1 + v2 * v2;
} while (s >= 1 || s == 0);
double multiplier = StrictMath.sqrt(-2 * StrictMath.log(s)/s);
nextNextGaussian = v2 * multiplier;
haveNextNextGaussian = true;
return v1 * multiplier;
}
}
并实现:Random.nextDouble
public double nextDouble() {
return (double) (((long)(next(26)) << 27) + next(27)) / (1L << 53);
}
首先,我想提请您注意一次生成 2 个值的事实,并且根据您是否知道自上次设置种子以来已通过的调用数,您可以对奇数与偶数调用使用略低的最大值。从现在开始,我将调用两个最大值v1_max和v2_max,指的是值是由 还是 生成的。nextGaussian
nextGaussian
v1 * multiplier
v2 * multiplier
答案
有了这个,让我们直接切入追逐,稍后解释:
| |Value |Seed* |
|------|------------------|---------------|
|v1_max|7.995084298635286 |97128757896197 |
|v2_max|7.973782613935931 |10818416657590 |
|v1_min|-7.799011049744149|119153396299238|
|v2_min|-7.844680087923773|10300138714312 |
* Seeds for v2 need to have nextGaussian called twice before you see the value listed.
仔细看看下一个高斯
@KaptainWutax和@Marco13的答案已经详细介绍了同样的事情,但我认为在图表上看到事情会让事情变得更清晰。让我们专注于v1_max,其他三个值具有非常相似的逻辑。我将在 x 轴、y 轴和 z 轴上绘制。v1
v2
v1 * multiplier
我们的眼睛立即跳到最大点 = 0, = 0, = 无穷大。但是,如果您在 do-while 循环中注意到,它明确不允许这种情况。因此,从图中可以清楚地看出,实际v1_max的值必须略高,但不会高得多。同样值得注意的是,对于任何> 0 的值,最大值为 = 0。v1
v2
v1 * multiplier
v1
v1
v1 * multiplier
v2
我们查找v1_max的方法是从零开始计数(或者更具体地说,从0.5开始计算生成它,按照 的实现,以2^-53的步长递增)。但是,只要知道,我们如何得到其他变量,以及v1
nextDouble
nextDouble
v1
v1 * multiplier
v1
倒车下一个双倍
事实证明,知道调用的输出就足以确定当时生成它的对象的种子。直观地说,这是因为从实现来看,它“看起来”应该有2 ^ 54个可能的输出 - 但的种子只有48位。此外,有可能在比蛮力快得多的时间内恢复这个种子。nextDouble
Random
nextDouble
Random
我最初尝试了一种天真的方法,方法是直接使用来获取种子的位,然后暴力破解剩余的21位,但这被证明太慢而没有用处。然后 SicksonFSJoe 给了我一种更快的方法,从单个调用中提取种子。请注意,要了解此方法的详细信息,您必须知道 的实现和一些模块化算术。next(27)
nextDouble
Random.next
private static long getSeed(double val) {
long lval = (long) (val * (1L << 53));
// let t = first seed (generating the high bits of this double)
// let u = second seed (generating the low bits of this double)
long a = lval >> 27; // a is the high 26 bits of t
long b = lval & ((1 << 27) - 1); // b is the high 27 bits of u
// ((a << 22) + c) * 0x5deece66d + 0xb = (b << 21) + d (mod 2**48)
// after rearranging this gives
// (b << 21) - 11 - (a << 22) * 0x5deece66d = c * 0x5deece66d - d (mod 2**48)
// and because modular arithmetic
// (b << 21) - 11 - (a << 22) * 0x5deece66d + (k << 48) = c * 0x5deece66d - d
long lhs = ((b << 21) - 0xb - (a << 22) * 0x5deece66dL) & 0xffffffffffffL;
// c * 0x5deece66d is 56 bits max, which gives a max k of 375
// also check k = 65535 because the rhs can be negative
for (long k = 65535; k != 376; k = k == 65535 ? 0 : k + 1) {
// calculate the value of d
long rem = (0x5deece66dL - (lhs + (k << 48))) % 0x5deece66dL;
long d = (rem + 0x5deece66dL) % 0x5deece66dL; // force positive
if (d < (1 << 21)) {
// rearrange the formula to get c
long c = lhs + d;
c *= 0xdfe05bcb1365L; // = 0x5deece66d**-1 (mod 2**48)
c &= 0xffffffffffffL;
if (c < (1 << 22)) {
long seed = (a << 22) + c;
seed = ((seed - 0xb) * 0xdfe05bcb1365L) & 0xffffffffffffL; // run the LCG forwards one step
return seed;
}
}
}
return Long.MAX_VALUE; // no seed
}
现在我们可以从 中获取种子,这样我们就可以迭代值而不是种子。nextDouble
v1
将一切整合在一起
该算法的概述如下:
- 初始化(代表 1)为 0.5
nd1
nextDouble
- 当上限和我们当前v1_max尚未跨越时,请重复步骤 3-7
- 递增 2^-53
nd1
- 计算自(如果存在),并生成 、 和
seed
nd1
nd2
v1
v2
s
- 检查
s
- 生成高斯,与v1_max进行比较
- 通过假设 = 0 设置新的上限
v2
这是一个Java实现。如果需要,您可以自己验证我上面给出的值。
public static void main(String[] args) {
double upperBound;
double nd1 = 0.5, nd2;
double maxGaussian = Double.MIN_VALUE;
long maxSeed = 0;
Random rand = new Random();
long seed;
int i = 0;
do {
nd1 += 0x1.0p-53;
seed = getSeed(nd1);
double v1, v2, s;
v1 = 2 * nd1 - 1;
if (seed != Long.MAX_VALUE) { // not no seed
rand.setSeed(seed ^ 0x5deece66dL);
rand.nextDouble(); // nd1
nd2 = rand.nextDouble();
v2 = 2 * nd2 - 1;
s = v1 * v1 + v2 * v2;
if (s < 1 && s != 0) { // if not, another seed will catch it
double gaussian = v1 * StrictMath.sqrt(-2 * StrictMath.log(s) / s);
if (gaussian > maxGaussian) {
maxGaussian = gaussian;
maxSeed = seed;
}
}
}
upperBound = v1 * StrictMath.sqrt(-2 * StrictMath.log(v1 * v1) / (v1 * v1));
if (i++ % 100000 == 0)
System.out.println(maxGaussian + " " + upperBound);
} while (upperBound > maxGaussian);
System.out.println(maxGaussian + " " + maxSeed);
}
最后要注意的一个问题是,此算法将为您提供 .要在 中使用它,您必须使用 的乘数(在上表中已经为您完成)Random
setSeed
Random
0x5deece66dL