在 Java 中,我有一个随机生成器,它生成从 -2^63 到 2^63 的随机数,这不是 java.util.Random。
我需要在(0,1)中生成随机双精度,这是我到目前为止所做的:
return (seed/(double)(9223372036854775807L))/2+0.5;//seed is a random long
这是正确的吗?是否有任何数值问题(下溢?)?
可以更好/更快吗?
谢谢你。
最快的方法可能只是将您的 long 中的前三位设置为0
,然后使用这些位进行双精度:
double rand = Double.longBitsToDouble(seed & 0x1FFFFFFFFFFFFFFFL);
这通过强制符号为正数和指数小于 0 来实现,这将导致尾数至少右移一次。假设 long 中的所有整数都是完全随机的,它给出了一个均匀分布。这是一个完整的 Java 程序,它使用 Random 生成随机长整数,然后使用此方法将它们转换为 0 到 1 之间的双精度数:
import java.util.Random;
class Main{
public static void main(String[] args){
Random rand = new Random();
long seed = rand.nextLong();
double x = Double.longBitsToDouble(seed & 0x1FFFFFFFFFFFFFFFL);
System.out.println(x);
}
}
这是 10 次执行的输出:
1.1211565592484309E-247
8.84224349357039E-242
6.956043405745214E-271
3.747746366809532E-232
9.302628573486166E-158
1.1440116527034282E-166
1.2574577719255876E-198
5.104999671234867E-269
3.360619724894072E-213
1.5654452507283312E-220
编辑
这给出了 0 和 1 之间所有可能双精度数的均匀分布。由于有更多的小双精度数,您可能永远不会看到接近 1 的数字。您可以通过基于现有位的位生成新指数来解决此问题,但是您需要一个循环来执行此操作,因此在将其考虑在内之后,它可能不是最快的方法:
long exponent = 0;
for(int i = 52; (seed >>> i & 1) > 0; i++) exponent++;
double x = Double.longBitsToDouble(seed & 0x000FFFFFFFFFFFFFL | ((1022 - exponent) << 52));
0.4773960377161338
0.929045618651037
0.7183096209363845
0.33962049395497845
0.45568660174922454
0.11670190555677815
0.09371618427480996
0.8192870898479095
0.9365016017283178
0.11311614413193898
我宁愿只看到一个部门。
0.5+(seed/1.84467440737096E+19);
也就是说,您将遇到浮点精度问题,因为您有 64 个随机整数位,然后您尝试将其压缩为 53 位双精度。为浮点值制作一个专用生成器可能会更好,但我不能肯定地说,因为我不知道你的动机。
我会使用Math.scalb作为最有效的方法,并确保没有由于舍入或表示错误而导致的有趣行为
double d = Math.scalb(seed >>> 1, -63);
您只能在双精度中使用 53 位,因此有些将被丢弃。
如果你跑
long seed = Long.MAX_VALUE;
System.out.println(Math.scalb(seed >>> 1, -63));
印刷
0.5
种子为 0 你得到 0.0
种子为 -1,你得到 1.0
不完全是。我认为更简单的方法是执行以下操作:
new Random(seed).nextDouble()
除非我误读了您需要从 0 到 1 的随机双精度,否则 Java 内置的 Math.random 就是这样做的。因此,您可以避免当前正在进行的所有转换。