亂數產生器

您輸入的內容不會被儲存、記錄或分享。

在任意範圍產生整數或小數亂數,並可選擇不重複。數值來自瀏覽器的密碼學亂數來源,而非 Math.random。

使用方式

  1. 設定最小值、最大值與需要的數量。
  2. 像樂透那樣的抽選請開啟「不重複」。
  3. 按「產生」取得新的一組,或複製整份清單。

數字從哪裡來

抽取使用 crypto.getRandomValues,也就是瀏覽器內建、具密碼學安全性的亂數產生器。它以作業系統收集的熵作為種子,設計上即使觀察輸出,也無法得知接下來會出現什麼的任何有用資訊。

顯而易見的替代方案 Math.random 則是另一種東西。它是為速度而打造的快速偽亂數產生器,通常是 xorshift128+,以類似時鐘狀態的東西作為種子,內部狀態小到只要少數幾個輸出,就可能足以還原並預測後續結果。ECMAScript 規格明白指出它不得用於密碼學用途。

用來抽獎時,這個差別很少造成影響。但這裡依然採用它,因為這麼做沒有任何成本,而且一個「只是有時候可信」的產生器,比「永遠可信」的產生器更難推敲。

取餘偏差

把亂數塞進某個範圍最簡單的做法,是對範圍大小取餘數;但只要範圍無法整除產生器的值域,這種做法就會有細微的錯誤。

假設取一個 0 到 255 的隨機位元組,想得到 1 到 10 之間的數字。位元組有 256 種可能、答案有 10 種,而 256 不是 10 的倍數。十個答案中有六個各對應 26 個位元組值,其餘四個各只對應 25 個,所以前六個出現的機率比其他高約 4%。範圍是 1 到 100 時偏差更大,1 到 200 時又更大。

解法是拒絕取樣:算出產生器值域頂端有多少個值會造成不平衡,抽到這些值就捨棄重抽。在這裡,超過範圍最大整數倍的值會被丟掉,而不是折回使用。偶爾需要多抽一次,但範圍內每個數字的機率都會完全相等。本站的密碼產生器與 UUID 產生器也使用同一套程序。

不重複抽取

不重複模式採取不放回抽樣,適用於樂透號碼、抽獎,以及從清單中隨機挑選一部分。一個值被抽出後就會從池中移除,所以同一組結果中不會出現重複的數字。

這需要範圍中的數值數量至少等於你要求的個數,四個數字的範圍不可能抽出六個不重複的數字;遇到這種情況,工具會直接說明,而不是無限迴圈。要注意這限制的是集合而不是順序:數字仍以隨機順序抽出,所以結果是隨機子集的洗牌,而不是排序好的樣本。如果想依序排列,可以另外選擇排序。

不適合的用途

它很適合抽獎、擲骰、測試資料、決定先做哪件事,以及各種人們交給運氣決定的小事。

它不是用來產生金鑰、密碼或權杖的工具,原因不是隨機性不足,而是這些東西需要特定的長度與字元集,請改用密碼產生器。它也不適合用於參加者必須信任結果的公開抽獎:頁面上出現的數字,對沒有親眼看著它出現的人來說什麼也證明不了;可驗證的抽獎需要公開的種子,以及在抽獎前做出的承諾,那是完全不同的設計。

偽隨機、密碼學隨機與真隨機

偽隨機產生器是一個決定性的函式:只要起始狀態相同,它就會永遠產生同一串數列。對重視可重現性的模擬來說這是優點,但只要有人可能試著預測下一個值,它就成了弱點。JavaScript 的 Math.random 通常是 xorshift128+,速度快,但內部狀態很小,只要拿到少數幾個輸出就能還原出來。

密碼學安全的產生器在設計上就是要讓你觀察再多輸出,也得不到關於其餘數列的有用資訊。它的種子來自作業系統蒐集的熵,例如時間抖動、中斷模式,以及 RDRAND 這類硬體指令,而且會定期重新設定種子。瀏覽器的 crypto.getRandomValues 和 Unix 的 /dev/urandom 是常見的介面,兩者都夠快,等於免費換到安全性。

來自熱雜訊或放射性衰變等物理來源的真隨機,存在於硬體模組和 random.org 這類服務中。但對幾乎所有用途來說都沒有必要:一個種子設定妥當的密碼學產生器,用任何人跑得出來的檢定都無法和它區分。

洗牌時不要讓結果偏掉

最直覺的洗牌做法,也就是用隨機比較函式排序,是錯的,而且錯得不容易一眼看出來。比較排序假設排序關係是一致的,所以丟一個隨機比較函式進去,會得到嚴重偏向某些排列的分布,結果還會隨排序實作而不同。看起來像洗過了,其實沒有。

正確的演算法是 Fisher-Yates:從列表尾端往前走,對每個位置在 0 到該位置之間(含該位置)取一個隨機索引,然後交換。每一種排列出現的機率都相同,而且只要線性時間。幾乎每個語言的標準函式庫都有正確的實作;沒有的話,這個迴圈也只要四行。

要抽出子集合,就是同一個操作提早停下來:洗前 k 個位置然後取走它們。用隨機索引抽、抽到重複就丟掉的做法也可以,但當 k 接近列表長度時效能會急遽變差,而那正是臨時寫的實作開始明顯空轉、久到你感覺得出來的地方。

真正的隨機序列長什麼樣

人類判斷隨機性的能力很差,而且錯的方向只有一種:真實的隨機資料裡,成團聚集的情況遠比直覺所能接受的多。丟一百次硬幣,連續出現六、七次正面是很可能發生的事,而不是可疑的事;反倒是完全沒有長連續的序列,比較可能是編出來的。

這也是為什麼串流服務的音樂隨機播放通常根本不隨機。真正的洗牌會頻繁地連播同一位歌手三次,頻繁到使用者回報說功能壞了,所以演算法會刻意把同一位歌手分散開來,比較不隨機,卻比較接近人們心中隨機播放的意思。

還有兩個相關的直覺值得抗拒:一是賭徒謬誤,以為某個結果在連續出現相反面之後就該輪到了;二是生日效應,它讓碰撞發生的機率遠高於預期,只要 23 個人,就有一半的機會有兩個人同一天生日。兩者都源自同一種誤判:低估了隨機性有多愛成團。

當你每次都想要同一組數字

模擬、程序化生成的內容和統計工作都需要可重現性:同一個種子產生同一串數列,結果才能重新檢驗,錯誤才能重建。密碼學產生器在這裡剛好是最不該用的工具,因為它無法讓你刻意指定種子。

通常的做法是選一個小而經過充分驗證的偽隨機產生器,例如 PCG、xoshiro,舊程式裡則是 Mersenne Twister,明確設定種子並記錄下來。在測試中,失敗時把種子印出來,就能把偶發的失敗變成可重現的失敗,而這正是會被修好的錯誤和被某人關掉的測試之間的差別。

把兩種產生器分開,絕不要讓有種子的那一個滲進和安全相關的程式碼。會出事的模式是:某個工具函式同時用來洗測試資料和產生 token,從那一刻起,一串決定性的數列就開始保護它不該保護的東西。

讓大家願意接受的公平抽籤

公平有兩個部分:機制,以及證據。這裡的機制沒有問題,用的是密碼學隨機來源加上拒絕取樣,範圍內每個值出現的機率都相同。證據則是一個網頁給不了的,因為螢幕上跳出一個數字,對沒在旁邊看的人來說什麼也證明不了。

當抽籤牽涉到金錢或義務時,標準做法是先承諾、後揭曉:在報名截止前先公布參加名單和種子的雜湊值,截止後用公開的方法進行抽籤,再把種子公布出來,讓任何人都能重算結果。這等於把對主辦者的信任換成人人都能驗算的算術。

至於日常情況,像是誰先上台報告、先做哪一項任務、最後一張票給誰,完全不需要這一套。這時真正重要的公平,是在場所有人能同時看到同一個結果出現;這也是為什麼一個大家看得見的抽籤,好過某人從自己螢幕上唸出來的數字。

常見問題

這些數字真的是隨機的嗎?
它們來自瀏覽器的密碼學安全亂數來源 crypto.getRandomValues,與 Math.random 不同。後者是以時鐘狀態初始化的快速偽亂數產生器,只要觀察到足夠輸出便可預測。
什麼是取餘偏差?為什麼重要?
直接把亂數對範圍大小取餘數,只有在生成器範圍能被整除時才會均勻,而實際上通常不能,於是範圍偏小的一端會略為容易出現。本工具會捨棄並重新抽取會造成偏差的數值,讓範圍內每個數字的機率相同。
最小值與最大值會被包含嗎?
會。範圍兩端都包含在內,因此設定 1 到 6 就正好是骰子的點數。
「不重複」的作用是什麼?
採用不放回抽樣,因此同一個值不會出現兩次,適合樂透式抽選或隨機挑選子集合。範圍大小必須不小於所需數量。
可以產生負數或非常大的範圍嗎?
可以。負數與跨越正負的範圍都支援,超過四十億的跨距也會以更寬的抽取方式處理,而不是被無聲截斷;後者是以 32 位元為前提的產生器常見的問題。

最後更新:

回報問題

請提供操作步驟、預期結果及實際結果。請移除機密與個人資料。

相關工具