浅析Java源码之Math.random()

澳门新浦京app下载 3

众所周知,随机数是任何一种编程语言最基本的特征之一。而生成随机数的基本方式也是相同的:产生一个0到1之间的随机数。看似简单,但有时我们也会忽略了一些有趣的功能。

从零自学java消遣一下,看书有点脑阔疼,不如看看源码!(๑╹◡╹)ノ”””

2.2、 并发包中ThreadLocalRandom类原理剖析

ThreadLocalRandom类是JDK7在JUC包下新增的随机数生成器,它解决了Random类在多线程下多个线程竞争内部唯一的原子性种子变量而导致大量线程自旋重试的不足。本节首先讲解下Random类的实现原理已经它在多线程下使用的局限性,然后引入ThreadLocalRandom类,通过讲解ThreadLocalRandom的实现原理来说明ThreadLocalRandom是如何解决的Random类的不足。

我们从书本上学到什么?

澳门新浦京app下载 ,​
JS中Math调用的都是本地方法,底层全是用C++写的,所以完全无法观察实现过程,Java的工具包虽然也有C/C++的介入,不过也有些是自己实现的。

2.2.1 Random类及其局限性

在JDK7之前包括现在java.util.Random应该是使用比较广泛的随机数生成工具类,另外java.lang.Math中的随机数生成也是使用的java.util.Random的实例。下面先看看java.util.Random的使用:

public class RandomTest {
    public static void main(String[] args) {

        //(1)创建一个默认种子的随机数生成器
        Random random = new Random();
        //(2)输出10个在0-5(包含0,不包含5)之间的随机数
        for (int i = 0; i < 10; ++i) {
            System.out.println(random.nextInt(5));
        }
    }
}
  • 代码(1)创建一个默认随机数生成器,使用默认的种子。
  • 代码(2)输出输出10个在0-5(包含0,不包含5)之间的随机数。

这里提下随机数的生成需要一个默认的种子,这个种子其实是一个long类型的数字,这个种子要么在Random的时候通过构造函数指定,那么默认构造函数内部会生成一个默认的值,有了默认的种子后,如何生成随机数那?

    public int nextInt(int bound) {
        //(3)参数检查
        if (bound <= 0)
            throw new IllegalArgumentException(BadBound);
        //(4)根据老的种子生成新的种子
        int r = next(31);
        //(5)根据新的种子计算随机数
        ...
        return r;
    } 

如上代码可知新的随机数的生成需要两个步骤

  • 首先需要根据老的种子生成新的种子。
  • 然后根据新的种子来计算新的随机数。

其中步骤(4)我们可以抽象为seed=f(seed),其中f是一个固定的函数,比如seed=
f(seed)=a*seed+b;步骤(5)也可以抽象为g(seed,bound),其中g是一个固定的函数,比如g(seed,bound)=(int)((bound
* (long)seed) >>
31);在单线程情况下每次调用nextInt都是根据老的种子计算出来新的种子,这是可以保证随机数产生的随机性的。但是在多线程下多个线程可能都拿同一个老的种子去执行步骤(4)计算新的种子,这会导致多个线程产生的新种子是一样的,由于步骤(5)算法是固定的,所以会导致多个线程产生相同的随机值,这并不是我们想要的。所以步骤(4)要保证原子性,也就是说多个线程在根据同一个老种子计算新种子时候,第一个线程的新种子计算出来后,第二个线程要丢弃自己老的种子,要使用第一个线程的新种子来计算自己的新种子,依次类推,只有保证了这个,才能保证多线程下产生的随机数是随机的。Random函数使用一个原子变量达到了这个效果,在创建Random对象时候初始化的种子就保存到了种子原子变量里面,下面看下next()代码:

    protected int next(int bits) {
        long oldseed, nextseed;
        AtomicLong seed = this.seed;
        do {
            //(6)
            oldseed = seed.get();
            //(7)
            nextseed = (oldseed * multiplier + addend) & mask;
            //(8)
        } while (!seed.compareAndSet(oldseed, nextseed));
        //(9)
        return (int)(nextseed >>> (48 - bits));
    }
  • 代码(6)获取当前原子变量种子的值
  • 代码(7)根据当前种子值计算新的种子
  • 代码(8)使用CAS操作,使用新的种子去更新老的种子,多线程下可能多个线程都同时执行到了代码(6)那么可能多个线程都拿到的当前种子的值是同一个,然后执行步骤(7)计算的新种子也都是一样的,但是步骤(8)的CAS操作会保证只有一个线程可以更新老的种子为新的,失败的线程会通过循环从新获取更新后的种子作为当前种子去计算老的种子,可见这里解决了上面提到的问题,也就保证了随机数的随机性。
  • 代码(9)则使用固定算法根据新的种子计算随机数。

总结下:每个Random实例里面有一个原子性的种子变量用来记录当前的种子的值,当要生成新的随机数时候要根据当前种子计算新的种子并更新回原子变量。多线程下使用单个Random实例生成随机数时候,多个线程同时计算随机数计算新的种子时候多个线程会竞争同一个原子变量的更新操作,由于原子变量的更新是CAS操作,同时只有一个线程会成功,所以会造成大量线程进行自旋重试,这是会降低并发性能的,所以ThreadLocalRandom应运而生。

最明显的,也是直观的方式,在Java中生成随机数只要简单的调用:

​ 本篇文章主要简单阐述Math.random()的实现过程。

2.2.2 ThreadLocalRandom

为了解决多线程高并发下Random的缺陷,JUC包下新增了ThreadLocalRandom类,下面首先看下它如何使用:

public class RandomTest {

    public static void main(String[] args) {
        //(10)获取一个随机数生成器
        ThreadLocalRandom random =  ThreadLocalRandom.current();

        //(11)输出10个在0-5(包含0,不包含5)之间的随机数
        for (int i = 0; i < 10; ++i) {
            System.out.println(random.nextInt(5));
        }
    }
}

如上代码(10)调用ThreadLocalRandom.current()来获取当前线程的随机数生成器。下面来分析下ThreadLocalRandom的实现原理。从名字看会让我们联想到基础篇讲解的ThreadLocal,ThreadLocal的出现就是为了解决多线程访问一个变量时候需要进行同步的问题,让每一个线程拷贝一份变量,每个线程对变量进行操作时候实际是操作自己本地内存里面的拷贝,从而避免了对共享变量进行同步。实际上ThreadLocalRandom的实现也是这个原理,Random的缺点是多个线程会使用原子性种子变量,会导致对原子变量更新的竞争,如下图:

澳门新浦京app下载 1

image.png

那么如果每个线程维护自己的一个种子变量,每个线程生成随机数时候根据自己老的种子计算新的种子,并使用新种子更新老的种子,然后根据新种子计算随机数,就不会存在竞争问题,这会大大提高并发性能,如下图ThreadLocalRandom原理:

澳门新浦京app下载 2

image.png

java.lang.Math.random()


Math隶属于java.lang包中,默认加载。本身是一个final类,方法都是静态方法,所以使用的时候不需要生成一个实例,直接调用Math.XX就行了。

2.2.3 源码分析

首先看下ThreadLocalRandom的类图结构:

澳门新浦京app下载 3

image.png

可知ThreadLocalRandom继承了Random并重写了nextInt方法,ThreadLocalRandom中并没有使用继承自Random的原子性种子变量。ThreadLocalRandom中并没有具体存放种子,具体的种子是存放到具体的调用线程的threadLocalRandomSeed变量里面的,ThreadLocalRandom类似于ThreadLocal类就是个工具类。当线程调用ThreadLocalRandom的current方法时候ThreadLocalRandom负责初始化调用线程的
threadLocalRandomSeed变量,也就是初始化种子。当调用ThreadLocalRandom的nextInt方法时候,实际上是获取当前线程的threadLocalRandomSeed变量作为当前种子来计算新的种子,然后更新新的种子到当前线程的threadLocalRandomSeed变量,然后在根据新种子和具体算法计算随机数。这里需要注意的是threadLocalRandomSeed变量就是Thread类里面的一个普通long变量,并不是原子性变量,其实道理很简单,因为这个变量是线程级别的,根本不需要使用原子性变量,如果还是不理解可以思考下ThreadLocal的原理。

其中变量seeder和probeGenerator是两个原子性变量,在初始化调用线程的种子和探针变量时候用到,每个线程只会使用一次。

另外变量instance是个ThreadLocalRandom的一个实例,该变量是static的,当多线程通过ThreadLocalRandom的current方法获取ThreadLocalRandom的实例时候其实获取的是同一个,但是由于具体的种子是存放到线程里面的,所以ThreadLocalRandom的实例里面只是与线程无关的通用算法,所以是线程安全的。

下面看看ThreadLocalRandom的主要代码实现逻辑

  • Unsafe 机制的使用

    private static final sun.misc.Unsafe UNSAFE;
    private static final long SEED;
    private static final long PROBE;
    private static final long SECONDARY;
    static {
        try {
            //获取unsafe实例
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            Class<?> tk = Thread.class;
            //获取Thread类里面threadLocalRandomSeed变量在Thread实例里面偏移量
            SEED = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomSeed"));
            //获取Thread类里面threadLocalRandomProbe变量在Thread实例里面偏移量
            PROBE = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomProbe"));
            //获取Thread类里面threadLocalRandomProbe变量在Thread实例里面偏移量
            SECONDARY = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomSecondarySeed"));
        } catch (Exception e) {
            throw new Error(e);
        }
    }
  • ThreadLocalRandom
    current()方法:该方法获取ThreadLocalRandom实例,并初始化调用线程中threadLocalRandomSeed和threadLocalRandomProbe变量。

    static final ThreadLocalRandom instance = new ThreadLocalRandom();
    public static ThreadLocalRandom current() {
        //(12)
        if (UNSAFE.getInt(Thread.currentThread(), PROBE) == 0)
            //(13)
            localInit();
        //(14)
        return instance;
    }

    static final void localInit() {
        int p = probeGenerator.addAndGet(PROBE_INCREMENT);
        int probe = (p == 0) ? 1 : p; // skip 0
        long seed = mix64(seeder.getAndAdd(SEEDER_INCREMENT));
        Thread t = Thread.currentThread();
        UNSAFE.putLong(t, SEED, seed);
        UNSAFE.putInt(t, PROBE, probe);
    }

如上代码(12)如果当前线程中threadLocalRandomProbe变量值为0(默认情况下线程的这个变量为0),说明当前线程第一次调用ThreadLocalRandom的current方法,那么就需要调用localInit方法计算当前线程的初始化种子变量。这里设计为了延迟初始化,不需要使用随机数功能时候Thread类中的种子变量就不需要被初始化,这是一种优化。

代码(13)首先计算根据probeGenerator计算当前线程中threadLocalRandomProbe的初始化值,然后根据seeder计算当前线程的初始化种子,然后把这两个变量设置到当前线程。
代码(14)返回ThreadLocalRandom的实例,需要注意的是这个方法是静态方法,多个线程返回的是同一个ThreadLocalRandom实例。

  • int nextInt(int bound)方法:计算当前线程的下一个随机数

    public int nextInt(int bound) {
        //(15)参数校验
        if (bound <= 0)
            throw new IllegalArgumentException(BadBound);
        //(16) 根据当前线程中种子计算新种子
        int r = mix32(nextSeed());
        //(17)根据新种子和bound计算随机数
        int m = bound - 1;
        if ((bound & m) == 0) // power of two
            r &= m;
        else { // reject over-represented candidates
            for (int u = r >>> 1;
                 u + m - (r = u % bound) < 0;
                 u = mix32(nextSeed()) >>> 1)
                ;
        }
        return r;
    }

如上代码逻辑步骤与Random相似,我们重点看下nextSeed()方法:

    final long nextSeed() {
        Thread t; long r; // 
        UNSAFE.putLong(t = Thread.currentThread(), SEED,
                       r = UNSAFE.getLong(t, SEED) + GAMMA);
        return r;
    }

如上代码首先使用 r = UNSAFE.getLong(t,
SEED)获取当前线程中threadLocalRandomSeed变量的值,然后在种子的基础上累加GAMMA值作为新种子,然后使用UNSAFE的putLong方法把新种子放入当前线程的threadLocalRandomSeed变量。

在所有其他语言中,生成随机数就像是使用Math工具类,如abs, pow, floor,
sqrt和其他数学函数。大多数人通过书籍、教程和课程来了解这个类。一个简单的例子:从0.0到1.0之间可以生成一个双精度浮点数。那么通过上面的信息,开发人员要产生0.0和10.0之间的双精度浮点数会这样来写:

​ 一步一步观察该方法,首先是java.lang.Math:

2.2.4 总结

本节首先讲解了Random的实现原理以及介绍了Random在多线程下存在竞争种子原子变量更新操作失败后自旋等待的缺点,从而引出ThreadLocalRandom类,ThreadLocalRandom使用ThreadLocal的原理,让每个线程内持有一个本地的种子变量,该种子变量只有在使用随机数时候才会被初始化,多线程下计算新种子时候是根据自己线程内维护的种子变量进行更新,从而避免了竞争。这里为后面高级篇讲解LongAdder的实现提供了基础,敬请期待
Java中高并发编程必备基础之并发包源码剖析 一书出版

Math.random() * 10
public final class Math {
  // 大量静态变量与方法
  // ...

  private static Random randomNumberGenerator;

  private static synchronized void initRNG() {
    if (randomNumberGenerator == null) 
      randomNumberGenerator = new Random();
  }

  public static double random() {
    if (randomNumberGenerator == null) initRNG();
    return randomNumberGenerator.nextDouble();
  }

  // ...other
}

而产生0和10之间的整数,则会写成:

​ 这里面与random相关的操作有3个:

Math.round(Math.random() * 10)

1、声明一个私有静态Random类randomNumberGenerator

进  阶

2、若randomNumberGenerator未初始化,调用new Random()将其初始化

通过阅读Math.random()的源码,或者干脆利用IDE的自动完成功能,开发人员可以很容易发现,java.lang.Math.random()使用一个内部的随机生成对象

一个很强大的对象可以灵活的随机产生:布尔值、所有数字类型,甚至是高斯分布。例如:

3、若randomNumberGenerator已经初始化,调用nextDouble方法并将其值返回

new java.util.Random().nextInt(10)

tips:synchronized关键字代表同步执行此方法,Java为多线程,所以为了保证randomNumberGenerator对象只被初始化一次,需要该关键字。比如两个线程同时调用了Math.random(),线程A发现rXX未被初始化,进入initRNG调用new
Random()方法。此时线程B也发现了rXX未被初始化,但是initRNG是同步方法,所以挂起等待线程A执行完毕。当线程A执行完后把rXX初始化了,所以在initRNG中的if判断,线程B会直接返回。

它有一个缺点,就是它是一个对象。它的方法必须是通过一个实例来调用,这意味着必须先调用它的构造函数。如果在内存充足的情况下,像上面的表达式是可以接受的;但内存不足时,就会带来问题。


所以简单来讲,random方法会在第一次调用时生成一个randomNumberGenerator对象,并调用其nextDouble方法生成随机数,之后的调用就只要持续调用此方法返回随机数就行了。

一个简单的解决方案,可以避免每次需要生成一个随机数时创建一个新实例,那就是使用一个静态类。猜你可能想到了java.lang.Math,很好,我们就是改良java.lang.Math的初始化。虽然这个工程量低,但你也要做一些简单的单元测试来确保其不会出错。

​ 下面来看Random类是个什么鬼,来源于java.util.Random:

假设程序需要生成一个随机数来存储,问题就又来了。比如有时需要操作或保护种子(seed),一个内部数用来存储状态和计算下一个随机数。在这些特殊情况下,共用随机生成对象是不合适的。

public class Random implements java.io.Serializable {
  // 静态变量
  /** use serialVersionUID from JDK 1.1 for interoperability */
  static final long serialVersionUID = 3905348978240129619L;

  private final AtomicLong seed;

  private final static long multiplier = 0x5DEECE66DL;
  private final static long addend = 0xBL;
  private final static long mask = (1L << 48) - 1;

  // constructor
  public Random() { this(++seedUniquifier + System.nanoTime()); }
  private static volatile long seedUniquifier = 8682522807148012L;

  public Random(long seed) {
    this.seed = new AtomicLong(0L);
    setSeed(seed);
  }

  // 设置种子
  synchronized public void setSeed(long seed) {
    seed = (seed ^ multiplier) & mask;
    this.seed.set(seed);
    haveNextNextGaussian = false;
  }

  // 产生大数字
  protected int next(int bits) {
    long oldseed, nextseed;
    AtomicLong seed = this.seed;
    do {
      oldseed = seed.get();
      nextseed = (oldseed * multiplier + addend) & mask;
    } while (!seed.compareAndSet(oldseed, nextseed));
    return (int)(nextseed >>> (48 - bits));
  }

  // 生成随机数
  public double nextDouble() {
    return (((long)(next(26)) << 27) + next(27))
      / (double)(1L << 53);
  }

  // 其他不关心的方法
  // nextBytes(bytes [])

  // nextInt

  // nextInt(int)

  // nextLong

  // nextBoolean

  // nextFloat

  // Serializable相关
}

并  发


上述代码剔除了大量的注释,还有一些不需要关心的方法,本文只关注Math.random()调用相关方法。

在Java
EE多线程应用程序的环境中,随机生成实例对象仍然可以被存储在类或其他实现类,作为一个静态属性。幸运的是,java.util.Random是线程安全的,所以不存在多个线程调用会破坏种子(seed)的风险。


对于这个类,首先来看看它的构造函数,理论上new一个Random实例是需要一个long类型的整数作为参数,但是代码用了this使其默认调用new
Random(long)这个构造函数。而在构造函数中又生成了一个新类并赋值给实例变量seed,关于这个AtomicLong类其实没啥好讲的,简单看一下就行:

另一个值得考虑的是多线程java.lang.ThreadLocal的实例。偷懒的做法是通过Java本身API实现单一实例,当然你也可以确保每一个线程都有自己的一个实例对象。

public class AtomicLong extends Number implements java.io.Serializable {
  private static final long serialVersionUID = 1927816293512124184L;

  // valueOffset相关...

  // 实例变量
  private volatile long value;
  // 构造函数
  public AtomicLong(long initialValue) {
    value = initialValue;
  }
  public AtomicLong() {}
  // 方法
  public final long get() {
    return value;
  }
  public final void set(long newValue) {
    value = newValue;
  }
  // 这个也会用到 但是不用关心具体实现
  public final boolean compareAndSet(long expect, long update) {
    return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
  }
  // 其余不需要关心(其实我也看不懂)的方法
}

虽然Java没有提供一个很好的方法来管理java.util.Random的单一实例。但是,期待已久的Java
7提供了一种新的方式来产生随机数:


如果思想简单一点,可以看出这个类也很简单,初始化传参赋值,set设置,get获取,多简单!

java.util.concurrent.ThreadLocalRandom.current().nextInt(10)


现在回到Random类的构造函数中,实例变量被赋值,类的value为初始化的0(后缀L代表这是一个long类型整数)。下一步调用setSeed,传入构造函数的long类型seed变量(不是seed类),其值为:

这个新的API综合了其他两种方法的优点:单一实例/静态访问,就像Math.random()一样灵活。ThreadLocalRandom也比其他任何处理高并发的方法要更快。
经验

++seedUniquifier + System.nanoTime()
// private static volatile long seedUniquifier = 8682522807148012L(8.6825e+15);
// 2^52 ~ 2^53
// 写文章时测试 => System.nanoTime() => 13230650355964(1.323e+13);

Chris Marasti-Georg 指出:


其中第一个变量为一个固定值,每次加1,另外一个为System.nanoTime(),该方法返回一个与当前时间相关的数字,具体我不关心。

Math.round(Math.random() * 10)


两个相加后,作为初始种子出传入setSeed方法中,方法第一步会对seed进行二次计算:

使分布不平衡,例如:0.0 –
0.499999将四舍五入为0,而0.5至1.499999将四舍五入为1。那么如何使用旧式语法来实现正确的均衡分布,如下:

seed = (seed ^ multiplier) & mask;
// private final static long multiplier = 0x5DEECE66DL;(25214903917 => 2.5214e+10)
// 2^34 ~ 2^35
// private final static long mask = (1L << 48) - 1;(2^48-1 => 0111...1 => 2^48 = 2.8147+e14)
Math.floor(Math.random() * 11)

​ 此处进行的是位运算,这里不用关心具体数值,只关注可能得到的最大最小值。

幸运的是,如果我们使用java.util.Random或java.util.concurrent.ThreadLocalRandom就不用担心上述问题了。

​ ^ => 异或运算:3 ^ 4 => 011 ^ 100 = 111 =>
7(不一样置1,否则置0)

Java实战项目里面介绍了一些不正确使用java.util.Random
API的危害。这个教训告诉我们不要使用:


可以看出,两个数字异或运算,假设其中较大的二进制位数为n,结果一定是小于等于2^n-1,比如3^4,4为100三位,所以结果一定小于等于2^3-1,即7。

Math.abs(rnd.nextInt())%n

​ & => 与运算:3 & 4 => 011 & 100 = 000 => 0(都为1置1,否则置0)

而使用:

​ 可以看出,与运算的结果总是小于等于较小的那个数。

rnd.nextInt(n)

​ 这样来再来看之前的位运算:

seed(2^52 ~ 2^53) ^ multiplier(2^34 ~ 2^35) => 0 ~ (2^53-1)

(seed ^ multiplier)(0 ~ 2^53-1) & mask(2^48-1) => 0 ~ 2^48-1

​ 结论是种子的范围是在0 ~ 2^48-1之间。

​ 测试代码:

public class test {
  public static void main(String [] args){
    pro b = new pro();
    System.out.println(b.getValue());
    // 256403749474577
    // 256458702577093
    // 256431328421593
  }
}
class pro{
  long seed = 8682522807148012L + System.nanoTime();
  long multiplier = 0x5DEECE66DL;
  long mask = (1L << 48) - 1;
  long getValue(){
    return (seed ^ multiplier) & mask;
  }
}


构造函数调用完后,现在来看nextDouble,这个方法除去位运算,本质上就是调用了两次next方法:

public double nextDouble() {
  return (((long)(next(26)) << 27) + next(27))
    / (double)(1L << 53);
}

​ 所以直接看next方法:

protected int next(int bits) {
  long oldseed, nextseed;
  AtomicLong seed = this.seed;
  do {
    oldseed = seed.get();
    nextseed = (oldseed * multiplier + addend) & mask;
  } while (!seed.compareAndSet(oldseed, nextseed));
  return (int)(nextseed >>> (48 - bits));
}


方法内部声明了2个long类型种子:oldseed、nextseed,通过get方法取得之前位运算得到的seed赋值给oldseed,然后再次通过运算得到一个nextseed的值,并传给seed.compareAndSet(oldseed,
nextseed)方法中。

​ 关于这个方法,源码里是这样的:

// java.util.concurrent.atomic.AtomicLong;
public class AtomicLong extends Number implements java.io.Serializable {
  public final boolean compareAndSet(long expect, long update) {
    return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
  }
}
// sun.misc.Unsafe.java
public native boolean compareAndSwapLong(Object obj, long offset,long expect, long update);


这个方法是个内部方法,也就是用C/C++实现的,所以有兴趣的自己去看源码,这里贴一个blog:


方法的用处简单讲也很简单,比较oldseed与内存中预期的值,如果符合,就将nextseed放进去。

​ 这里的运算也不管具体数值,oldseed *
multiplier按最大计算会出现溢位,截取成long类型后的大小不确定,所以按照与运算这里的范围依然是0
~ mask,即0 ~ 2^48-1。

​ 最后返回(int)(nextseed >>> (48 –
bits)),这里对结果进行类型处理,贴一个类型范围图:

基本类型 最小值 最大值
byte -2^7 2^7 – 1
short -2^15 2^15 – 1
int -2^31 2^31 – 1
long -2^63 2^63 – 1

​ 若结果是大于int类型最大值,超出的部分会被直接截取砍掉。

​ 最后看nextDouble的计算式:

(((long)(next(26)) << 27) + next(27)) / (double)(1L << 53)

​ 传入的bits分别为26与27,这时返回的随机数为:

(int)(nextseed >>> 22) 与 (int)(nextseed >>> 21)

​ >>>为无符号右移,具体意思就不解释了。

​ 得到的结果范围大概是 0 ~
2^26(27)-1,理论上在这里是不会超过int的最大值。

​ 当seed(测试代码中的tmp)为mask时,此时计算会达到最大值:

(((long)(1L << 53)-1 ) / (double)(1L << 53)

​ 测试代码:

public class test {
  public static void main(String [] args){
    testb bb = new testb();
    long a = (long)bb.getNext(26);
    long b = bb.getNext(27);
    double c = 1L << 53;
    double d = ((a<<27) +b)/c;
    // 0.99999999...
    System.out.println(d);
  }
}
class testb{
  long tmp = (1L<<48)-1;
  // long tmp = 0 => 0.0
  int getNext(int num){
    return (int)(tmp >>> (48 - num));
  }
}

​ 当测试代码中tmp为0时,计算结果为最小值0。

​ 每一次调用nextDouble,会生成不一样的seed,也就会返回不一样的数字。

​ 这样就是整个随机数生成过程。

​ 完结,撒花ヽ(゚∀゚)メ(゚∀゚)ノ

You can leave a response, or trackback from your own site.

Leave a Reply

网站地图xml地图