素數斷定 高級程序員才知道的那些事兒

在信息安全領域,常常須要用到一些大素數,好比著名的RSA算法就必須依賴到兩個大素數。幸運的是天然數中素數還真很多(很簡單就能證實素數有無窮多個),並且密度也不算低,因此找到一個素數不是那麼難,但讓你找一個能用在RSA算法裏的素數就比較難了。java

暴力試除

試想下,若是如今讓你去尋找出一個素數,你會怎麼辦?記得剛上大學剛學會C語言基本語法後,有道課後題就是斷定一個數是不是素數,具有基本編程能力的人必定能寫出以下代碼:算法

boolean checkPrime(int n) {
    for (int i = 2; i*i <= n; i++) {
        if (n%i == 0) {
            return false;
        }
    }
    return true;
}

素數斷定最簡單的方法就是試除,也就是上面代碼。它的原理是從2到根號n,看n是否能被某個數除盡,若是能那n確定不是素數,反之必定是素數。這確實是個簡單粗暴且正確的方法,惟一的問題是它太慢了,斷定一個數的時間複雜度是O(n)。若是讓你用這種方法去判斷一個幾百位的數是不是素數,那可能用如今最早進的計算機,也須要n多年才能算出來。編程

篩選法

固然素數斷定還有一個更快的批量斷定算法——埃氏篩選,他找到n之內的全部素數只須要O(n log log n)的時間複雜度。數組

在這裏插入圖片描述
其原理是這樣的,設置一個標記數組,開始先把2的全部倍數都標記了,而後日後走發現3沒有被標記,那3確定是個素數,而後在標記數組中把全部3的倍數標記掉,而後發現4已經被標記了 跳過,到5……,直到標記完全部數字,那麼剩下未標記的數字就是素數了,見上圖,代碼以下:安全

int[] signs = new int[n+1];
void eratosthenes(int n) {
    for (int i = 2; i <= n; i++) {
        if (signs[i] == 0) {
            for (int j = i * i; j <= n; j += i) {
                signs[j] = 1;
            }
        }
    }
}

埃氏篩選法雖然看起來比較快,但他也有本身的問題。首先他只能批量,對單個的n斷定時也是須要篩出全部小於n的素數的。其次,它還須要依賴存儲空間來存儲標記。因此它仍然沒法被用在超大素數的斷定上。 測試

有沒有更快找到一個素數的方法?自從中世紀以來,有好多的數學家都在致力於尋找傳中的素數公式。好比歐拉在1772年發現,$f(n) = n^2 + n + 41$ 當n小於41時 f(n)的值都是素數,雖而後來也有數學家相繼發現了能生成更大素數的公式,但這些公式能生成的數依舊是頗有限的。到了高斯時代,基本上確認了簡單的質數公式是不存在的,所以,高斯認爲對素性斷定是一個至關困難的問題。優化

費馬小定理

/img/bVcR1bN
然而,事情老是有起色的。讓咱們一塊兒回到1636年,著名數學家費馬在一封信中寫出這樣一個公式。spa

若是p是一個素數,且a不是p的倍數,則有a^(p-1) ≡ 1(mod p)

後來證實a不是p的倍數這個條件不是必須的。 這個定理的含義就是隻要p是素數,那麼$(a^(p-1))mod p$恆等於1,這就是著名的費馬小定理。可能你已經在想,能不能用這個定理來斷定素數,確實費馬小定理反過來也幾乎是成立的,若是一個數p能使得a^(p-1) ≡ 1(mod p),p有很大機率是個素數,注意這裏是幾乎成立。code

public class PrimeNumCheck {
    public static boolean check(long a, long p) {
        long res = fastMod(a, p-1, p);
        return res == 1;
    }

    public static long fastMod(long x, long n, long m) {
        if (n == 1) {
            return x % m;
        }
        long tmp = fastMod(x, n>>1, m);
        if (n % 2 == 0) {
            return (tmp * tmp) % m;
        } else {
            return (tmp * tmp * x) % m;
        }
    }
    
    public static void main(String[] args) {
        System.out.println(check(2, 7));
    }
}

用如上Java代碼,能夠快速的機率性斷定一個數是不是素數(斷定結果不是100%準確),這也取決於上述代碼中a的選擇。上面用到了快速冪算法,能將對一個數的n次冪取模的時間複雜度降到O(logn)。咱們彷佛能夠將素數的斷定時間複雜度從O(n)下降到O(logn),這是質的飛躍,從原來的幾乎不可計算變爲可計算,這才爲大素數的應用鋪平了道路。圖片

可是別急,它還有些小缺陷。我剛說了費馬小定理反過來是幾乎成立的,我一直在強調幾乎二字。由於有些和數n也能使得$a^(n-1) ≡ 1(mod n)$成立,這些使得$a^(n-1) ≡ 1(mod n)$的合數被稱爲基於a僞素數,好比前幾個基於2的僞素數分別是34一、56一、645……。不過這種僞素數也很是少,實際上,對於一個512位的數,其中基於2的僞素數不到1/10^20,若是是1024位的數的話,僞素數機率就只有不到1/10^41了。這個機率究竟有多低,舉個例子,你能隨機找到一個512位基於2的僞素數的機率比你中五百萬大獎的機率都小。 因此你是隨機找一個素數,基於2的費馬小定理斷定已經足夠用了。

固然若是你非要追求更高準確率的話,仍是能夠優化的,畢竟基於2的僞素數並不必定是基於其餘a的僞素數,因此咱們能夠多換幾個不一樣的a來進一步提高上述代碼的準確性。 但歷史告訴咱們凡事總有意外。有些合數對於任意的a都能使得費馬定理成立,這些數被稱爲卡邁克爾數(Carmichael Number),前幾個卡邁克爾數分別是561 1105 1729…… 關於卡邁克爾數又是另外一個故事了。

小結

費馬小定理這種機率性的解法給了咱們解決問題的一種新思路,就比如用布隆過濾器同樣,它們都不是百分百準確,但能夠在準確性可控的狀況下獲得更高效的解決方案。計算機的世界不只能夠用空間換時間,還能夠用準確率換時間。

像費馬定理這種神奇的數學定理,我感受這彷佛是上帝在造物時埋下的一個關於數字的小彩蛋,而我也堅信這種小彩蛋還有不少,沒準那天咱們能夠發現上帝隱藏在圓周率裏的笑話呢!!

參考資料

相關文章
相關標籤/搜索