NTU-CE
Computer Programming
Java
Programming Language
我們知道Java提供兩種方式讓我們產生亂數:
這兩者「使用」上,最大的差別是Random可以設亂數種子,但是當程式設下亂數種子後,發現每次產生的數字序列竟然是固定的?可是,亂數不是應該要很亂嗎?
直接看碼:
不管執行幾百遍都是輸出這樣:
nextInt(): -1150867590 -1704868423 884779003 -29161773 -885414485 -1791719506 700408466 -1654940986 665796387 -1584522320
nextInt(45): 41 32 5 0 8 41 24 28 7 21
如果我們把創建Random物件的方式改為no-arg constructor:
每次的執行結果就會不一樣了。
由於網路上難找解釋技術實作好讀的文章,所以就不想再花力氣另外寫導讀了,請自行閱讀電腦的隨機數是如何做到的?,有個概觀後,就來看Java又是怎麼實作Random的呢?
我們去看GNU維護的類JDK所提供的java.util.Random實作方式(雖然不是你們用的Oracle JDK,但是很像)。
(私語:GNU就是堅持什麼都要開放,有別於什麼都要吉吉吉的公司…)
我們來解說幾個範例有用到的public method:
如果你要讓每次的執行結果不一樣,就不要設固定的亂數種子,採用Random()。
發現好玩的事情了嗎?this()是什麼?
我們之前講過this就是指「Random這個物件」,所以this()在這裡就是用來呼叫「Random(long seed)」。故就算你用no-arg的Random()去創建物件,它其實也是去呼叫Random(long seed),只是只是!!!它幫你傳入的亂數種子是「當下時間」,又因為你每次執行程式的當下時間一定不一樣(可能差幾毫秒鐘),所以乍看之下,我們誤以為Random()創建的物件所得到的亂數是不固定的。
如果看不懂上一段講什麼,那白話文就是
等價
結束。
我們來看看那Random(long seed)在做什麼?
seed是Random的private long data field,然後又要去看setSeed(long seed)在做什麼。
我們需要解讀this.seed被設成什麼?
先備知識:
用0x開頭表示十六進位的數字。
數字以L結尾,代表那個數字的型態是long。
首先((1L << 48) - 1)
為,因為把二進位的1向左移48個位置再補0,轉成十進位,再減1。
再來看0x5DEECE66DL
在不同的進位制是多少?
Binary Digits | 10111011110111011001110011001101101 |
Decimal Digits | 25214903917 |
Hexadecimal Digits | 5DEECE66D |
全部查完了,你就能恍然明白
this.seed = (seed ^ 0x5DEECE66DL) & ((1L << 48) - 1);
為什麼要這樣設嗎?
才有鬼!
我們先繼續把這幾個用到的method之間呼叫的關係釐清。
根據範例輸出發現nextInt()會回傳正整數或負整數,而其中的祕密藏在next()。
根據範例輸出發現nextInt(n)會回傳0到n-1之間的正整數,細看實作:
(n & -n) == n
成真,則n是2的某次方?我們看見每呼叫一次next()就會更新一次this.seed,其實所有的祕密都藏在next()的實作裡,因為nextInt()或nextInt(int n)都是呼叫next()並把它回傳的值透過固定的方式限制於某個數值以內。
觀察nextInt(int n)和next()的實作可以很明顯地發現Random是採用導讀提到的線性同餘法(Linear Congruential Formula)來產生新亂數。到這裡我發現若要以數學角度去解釋這種實作方式的位元池夠不夠大(也就是亂數夠不夠亂),以目前的能力來說有困難,因為我們連最基本的Bitwise Operator都不熟悉。權衡之下,與其去借來The Art of Computer Programming囫圇吞下一堆數學符號,不如我們借這次機會熟悉那些運算子。
為了釐清位元運算子的祕密,我們重新兜湊幾個在Random裡面關鍵的method並加上適當的階段性輸出。
範例輸出:
直接看碼:
利用Math.random()得到介於1到10之間正整數的作法。
直接看碼:
解釋一下:
0 <= Math.random() < 1
0 <= Math.random() * a < a
b <= Math.random() * a + b < a + b
然後我們就可以得到介於b(含)到a+b(不含)之間的亂數了。
Donald E. Knuth. 1997. The Art of Computer Programming, Volume 2 (3rd Ed.): Seminumerical Algorithms. Addison-Wesley Longman Publishing Co., Inc., Boston, MA, USA.