在Java中調用這個Math.Random()函數可以返回帶正號的double值,取值範圍是[0.0,1.0)的左閉右開區間,返回值是一個僞隨機選擇的數,在該範圍內(近似)均勻分佈。 html
Java的API中是這樣描述Random()函數的: java
僞隨機,也就是有規則的隨機,所謂有規則的就是在給定種(seed)的區間內隨機生成數字。 算法
相同種子數的Random對象,相同次數生成的隨機數字是徹底相同的。 數據庫
Random類中各方法生成的隨機數字都是均勻分佈的,也就是說區間內部的數字生成的概率均等。 數組
下面是Java.util.Random()方法摘要 dom
protected int next(int bits):生成下一個僞隨機數。 函數
boolean nextBoolean():返回下一個僞隨機數,它是取自此隨機數生成器序列的均勻分佈的boolean值。 ui
void nextBytes(byte[] bytes):生成隨機字節並將其置於用戶提供的 byte 數組中。 this
double nextDouble():返回下一個僞隨機數,它是取自此隨機數生成器序列的、在0.0和1.0之間均勻分佈的 double值。 spa
float nextFloat():返回下一個僞隨機數,它是取自此隨機數生成器序列的、在0.0和1.0之間均勻分佈float值。
double nextGaussian():返回下一個僞隨機數,它是取自此隨機數生成器序列的、呈高斯(「正態」)分佈的double值,其平均值是0.0標準差是1.0。
int nextInt():返回下一個僞隨機數,它是此隨機數生成器的序列中均勻分佈的 int 值。
int nextInt(int n):返回一個僞隨機數,它是取自此隨機數生成器序列的、在(包括和指定值(不包括)之間均勻分佈的int值。
long nextLong():返回下一個僞隨機數,它是取自此隨機數生成器序列的均勻分佈的 long 值。
void setSeed(long seed):使用單個 long 種子設置此隨機數生成器的種子。
方法摘要也就這些,下面給幾個例子:
1.生成[0,1.0)區間的小數:double d1 = r.nextDouble();
2.生成[0,5.0)區間的小數:double d2 = r.nextDouble() * 5;
3.生成[1,2.5)區間的小數:double d3 = r.nextDouble() * 1.5 + 1;
4.生成-231到231-1之間的整數:int n = r.nextInt();
5.生成[0,10)區間的整數:
int n2 = r.nextInt(10);//方法一
n2 = Math.abs(r.nextInt() % 10);//方法二
Java中的Random類生成的是僞隨機數,使用的是48-bit的種子,而後調用一個linear congruential formula線性同餘方程
那麼什麼是線性同餘方程呢?
古老的LCG(linear congruential generator)表明了最好最樸素的僞隨機數產生器算法。主要緣由是容易理解,容易實現,並且速度快。
LCG 算法數學上基於公式:
X(n+1) = (a * X(n) + c) % m
其中,各系數爲:
模m, m > 0
係數a, 0 < a < m
增量c, 0 <= c < m
原始值(種子) 0 <= X(0) < m
其中參數c, m, a比較敏感,或者說直接影響了僞隨機數產生的質量。
通常而言,高LCG的m是2的指數次冪(通常2^32或者2^64,Java中是2^48),由於這樣取模操做截斷最右的32或64位就能夠了。多數編譯器的庫中使用了該理論實現其僞隨機數發生器random()。
剛剛說了,Java調用了一個線性同餘方程來實現僞隨機數的產生,具體的計算以下:
Xi = (Xi-1 * A + C ) mod M
其中A,C,M都是常數(通常會取質數)。當C=0時,叫作乘同餘法。引出一個概念叫seed,它會被做爲X0被代入上式中,而後每次調用random()函數都會用上一次產生的隨機值來生成新的隨機值。能夠看出實際上用random()函數生成的是一個遞推的序列,一切值都來源於最初的 seed。因此當初始的seed取同樣的時候,獲得的序列都相同。
下面Random()的兩種構造方法
Random():建立一個新的隨機數生成器。
Random(long seed):使用單個 long 種子建立一個新的隨機數生成器。
咱們在構造Random對象的時候指定種子,若是是第一種構造方法,則默認當前系統時間對應的相對時間有關的數字做爲種子數
須要說明的是:你在建立一個Random對象的時候能夠給定任意一個合法的種子數,種子數只是隨機算法的起源數字,和生成的隨機數的區間沒有任何關係。
public Random() { this(seedUniquifier() ^ System.nanoTime()); } private static long seedUniquifier() { // L'Ecuyer, "Tables of Linear Congruential Generators of // Different Sizes and Good Lattice Structure", 1999 for (;;) { long current = seedUniquifier.get(); long next = current * 181783497276652981L; if (seedUniquifier.compareAndSet(current, next)) return next; } }
這裏使用了System.nanoTime()方法來獲得一個納秒級的時間量,參與48位種子(爲何要48位)的構成,而後還進行了一個很變態的運算——不斷乘以181783497276652981L,直到某一次相乘先後結果相同(彷佛不是相同,可是其doc文檔是這樣說明的,若是有清楚的朋友能夠下面留言說明)——來進一步增大隨機性,這裏的nanotime能夠算是一個真隨機數,不過有必要提的是,nanoTime和咱們經常使用的currenttime方法不一樣,返回的不是從1970年1月1日到如今的時間,而是一個隨機的數——只用來先後比較計算一個時間段,好比一行代碼的運行時間,數據庫導入的時間等,而不能用來計算今天是哪一天。
到目前爲止,這個程序已經至少進行了三次隨機:
一、得到一個長整形數做爲「初始種子」(系統默認的是8682522807148012L(8682522807148012L與181783497276652981L的意義))
二、不斷與181783497276652981L相乘,直到某一次相乘先後數值相等
三、與系統隨機出來的nanotime值做異或運算,獲得最終的種子
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)); }next()函數是random()的核心函數,也是對線性同餘的實現。
(oldseed * multiplier + addend) & mask;是否是形如 (Xi-1 * A + C ) mod M?只是mod變成了&,爲何呢?
private static final long multiplier = 0x5DEECE66DL; private static final long addend = 0xBL; private static final long mask = (1L << 48) - 1;其中multiplier和addend分別表明公式中的a和c,很好理解,但mask表明什麼呢?其實,x & [(1L << 48)–1]與 x(mod 2^48)等價。解釋以下:
而咱們將x對2^N取餘操做但願達到的目的能夠理解爲:
一、全部比2^N位(包括2^N那一位)高的位全都爲0
二、全部比2^N低的位保持原樣
因此x & [(1L << 48)–1]與 x(mod 2^48)等價。
接着讓咱們看看nextInt()的實現。
public int nextInt() { return next(32); }默認調用next,生成32位的隨機數。
public int nextInt(int n) { if (n <= 0) throw new IllegalArgumentException("n must be positive"); if ((n & -n) == n) // i.e., n is a power of 2 return (int)((n * (long)next(31)) >> 31); int bits, val; do { bits = next(31); val = bits % n; } while (bits - val + (n-1) < 0); return val; }顯然,這裏基本的思路仍是同樣的,先調用next函數生成一個31位的隨機數(int類型的範圍),再對參數n進行判斷,若是n剛好爲2的方冪,那麼直接移位就能夠獲得想要的結果;若是不是2的方冪,那麼就關於n取餘,最終使結果在[0,n)範圍內。另外,do-while語句的目的應該是防止結果爲負數。
那麼爲何(n & -n) == n能夠判斷一個數是否是2的次方冪?
衆所周知,計算機中負數使用補碼儲存的:
2 :0000 0010 -2 :1111 1110
8 :0000 1000 -8 :1111 1000
18 :0001 0010 -18 :1110 1110
20 :0001 0100 -20 :1110 1100
補碼有一個特性,就是能夠對於兩個相反數n與-n,有且只有最低一個爲1的位數字相同且都爲1,而更低的位全爲0,更高的位各不相同。所以兩數做按位與操做後只有一位爲1,而能知足這個結果仍爲n的只能是本來就只有一位是1的數,也就是剛好是2的次方冪的數了。1. http://blog.sina.com.cn/s/blog_93dc666c0101h3gd.html
2. http://www.cnblogs.com/xkfz007/archive/2012/03/27/2420154.html
3. http://t1174779123.iteye.com/blog/2037719
4. http://www.importnew.com/12460.html