簡體   English   中英

尋找最近的較小素數的快速算法

[英]Fast algorithm for finding the nearest smaller prime number

例如:- 如果給定數字是 10,我們必須返回 7(因為它是最接近的較小素數)

我能想到的方法是這樣的:-
Mainloop:測試給定數字是否為素數(通過應用素數測試),
如果它是素數,則返回數字,否則將數字減 1 並轉到 Mainloop。

但我必須在 long long int 范圍上工作,這需要很多時間。

有沒有更好的方法,如果我應該用上述方法 go 只有那么我應該使用哪個素性測試?

如果輸入的大小有界,則在預先計算的素數表中查找可能是最快的。

除上述內容外,還應注意Bertrand的假設指出,始終存在至少一個質數p,其中n<p<2n-2 這樣就為您提供了一個上限。

查看Miller-Rabin素數檢驗 這是概率性的,但是如果您執行數百次,則幾乎可以保證long long內的精度。

另外,如果可以使用Java,則BigInteger.isProbablePrime也可以提供幫助。 C \\ C ++似乎沒有用於測試素數的內置函數。

這是Daniel Fischer在評論中提到的Baillie-Wagstaff偽素性測試的偽代碼實現。 我們從一個簡單的Eratosthenes篩子開始,稍后我們將需要它。

function primes(n)
    ps := []
    sieve := makeArray(2..n, True)
    for p from 2 to n step 1
        if sieve(p)
            ps.append(p)
            for i from p * p to n step p
                sieve[i] := False
    return ps

powerMod函數將所有基數bm為模將底數b增大到指數e 它比先執行取冪然后取結果的模數要快得多,因為中間計算量很大。

function powerMod(b, e, m)
    x := 1
    while e > 0
        if e % 2 == 1
            x := (b * x) % m
        b := (b * b) % m
        e := floor(e / 2)
    return x

數論的jacobi函數表明a是否為二次余數mod p

function jacobi(a, p)
    a := a % p
    t := 1
    while a != 0
        while a % 2 == 0
            a := a / 2
            if p % 8 == 3 or p % 8 == 5
                t := -t
        a, p := p , a # swap
        if a % 4 == 3 and p % 4 == 3
            t := -t
        a := a % p
    if p == 1 return t else return 0

加里·米勒(Gary Miller)的強偽素數檢驗基於皮埃爾·德·費馬(Pierre de Fermat)的Little定理 ,該定理指出,如果p是素數,則對於任何a != 0, a ^( p -1)== 1(mod p )。 Miller的測試比Fermat的測試要強一些,因為它不能被Carmichael Numbers欺騙。

function isStrongPseudoprime(n, a)
    d := n - 1; s := 0
    while d % 2 == 0
        d := d / 2; s := s + 1
    t = powerMod(a, d, n)
    if t == 1 return ProbablyPrime
    while s > 0
        if t == n - 1 return ProbablyPrime
        t := (t * t) % n; s := s - 1
    return Composite

Miller-Rabin測試執行k個強偽素數測試,其中k通常在10到25之間。強偽素數測試可以被欺騙,但是如果您執行了足夠多的測試,被欺騙的可能性很小。

function isPrime(n) # Miller-Rabin
    for i from 1 to k
        a := randInt(2 .. n-1)
        if not isStrongPseudoprime(n, a)
            return Composite
    return ProbablyPrime

該素性測試足以滿足大多數目的,並且速度足夠快。 但是,如果您想要更強一點,更快一點的東西,則可以使用基於Lucas鏈的測試。 這是盧卡斯鏈的計算。

function chain(n, u, v, u2, v2, d, q, m)
    k := q
    while m > 0
        u2 := (u2 * v2) % n; v2 := (v2 * v2 - 2 * q) % n
        q := (q * q) % n
        if m % 2 == 1
            t1 := u2 * v; t2 := u * v2
            t3 := v2 * v; t4 := u2 * u * d
            u, v := t1 + t2, t3 + t4
            if u % 2 == 1 u := u + n
            if v % 2 == 1 v := v + n
            u, v, k := (u / 2) % n, (v / 2) % n), (q * k) % n
        m := floor(m / 2)
    return u, v, k

由於約翰·塞爾弗里奇,通常使用算法初始化盧卡斯鏈。

function selfridge(n)
    d, s := 5, 1; ds := d * s
    repeat
        if gcd(ds, n) > 1 return ds, 0, 0
        if jacobi(ds, n) == 1 return ds, 1, (1 - ds) / 4
        d, s := d + 2, s * -1; ds := d * s

然后,Lucas偽素數測試確定一個數字是素數還是可能是合成數。 像Fermat測試一樣,它具有標准和強壯兩種風格,並且像Fermat測試一樣,它可以被愚弄,盡管使用Fermat測試時,錯誤之處在於復合數字可能被錯誤地報告為質數,但是使用Lucas測試時,則錯誤是素數可能不正確地報告為合成。

function isLucasPseudoprime(n) # standard
    d, p, q := selfridge(n)
    if p == 0 return n == d
    u, v, k := chain(n, 0, 2, 1, p, d, q, (n + 1) / 2)
    return u == 0

function isLucasPseudoprime(n) # strong
    d, p, q := selfridge(n)
    if p == 0 return n == d
    s, t := 0, n + 1
    while t % 2 == 0
        s, t := s + 1, t / 2
    u, v, k := chain(n, 1, p, 1, p, d, q, t // 2
    if u == 0 or v == 0 return Prime
    r := 1
    while r < s
        v := (v * v - 2 * k) % n; k := (K * k) % n
        if v == 0 return Prime
    return ProbablyComposite

然后,Baillie-Wagstaff檢驗很簡單。 首先檢查輸入是否小於2或正整數(檢查平方根是否為整數)。 然后按小於100的素數進行試驗除法,很快就找到了大多數復合詞,最后對基數2進行了強大的偽素數測試(當然,有些人在基數3上添加了強大的偽素數測試),再由Lucas偽素數測試進行了最終確定。 。

function isPrime(n) # Baillie-Wagstaff
    if n < 2 or isSquare(n) return False
    for p in primes(100)
        if n % p == 0 return n == p
    return isStrongPseudoprime(n, 2) \
       and isLucasPseudoprime(n) # standard or strong

Baillie-Wagstaff檢驗沒有已知的錯誤。

一旦有了良好的素數測試,就可以通過從n倒數到第一個素數停止,找到小於n的最大素數。

如果您對使用質數編程感興趣,我建議在我的博客中推薦這篇文章 ,或與質數有關的許多其他博客條目,您可以通過在博客上使用搜索功能找到這些文章。

你有非常有趣的任務!

我決定在純 C++(編譯時需要 C++20 標准)中從頭開始為您實現非常先進、大型但快速(高效)的解決方案。

在我的代碼中使用了以下補充算法 -埃拉托色尼篩法費馬素性檢驗米勒拉賓素性檢驗試驗除法(使用素數)、二分搜索

還使用外部 C++ 庫Boost Multiprecision來實現大 integer 算術。 如果您願意,您可以使用其他庫,我的代碼是以可以使用任何庫的方式制作的。 CLang/GCC 編譯器也有__int128類型,如果您的任務僅在 128 位范圍內,您也可以使用它來代替大整數。

我的代碼支持任何位大小的整數,但我徹底測試了正確性和速度直到 1024 位(即測試了 8、16、32、64、128、256、512、1024 位)。

我盡可能使用多核進行所有計算,如果您有 8 個內核(實際上是硬件線程),那么在所有計算中我啟動一個 8 個線程池並使用它們。 但是這種多線程方法僅用於大於 128 位的整數,這是一種默認啟用的啟發式算法,但如果需要,您可以通過將額外參數設置為主要 function PrevPrime()

我的主要算法( PrevPrime()函數)執行以下步驟:

  1. 有一次(整個程序運行一次)我使用埃拉托色尼篩法計算了一張小於 2^20 的素數表。 該表最多可容納 1024 位整數。 如果您使用 2048 位或更大,則可以使用更多的素數。

  2. 當我們給定 N 時,我開始從 N 向下移動,並再次使用埃拉托色尼篩從范圍(N - 長度,N] 中過濾出合數。並非所有 2^20 素數都被使用,我通過實驗計算了哪些素數對於每個輸入 N 位大小是最佳的(對於 32 位 100 個素數就足夠了,對於 64 位 500 個素數,對於 128 位 - 1200 個素數,對於 256 位 - 7000 個素數,對於 512 位 - 17500 個素數,對於 1024 位 - 75000 個素數)。

  3. 埃拉托色尼篩需要從某個偏移量N - off開始篩選每個特定的素數p ,其中off = N % p 因為除法在 CPU 的基本操作中是最昂貴的,所以我使用多線程(多核)方法(對於更大的整數,使用更小的單線程)來計算偏移量。

  4. 篩分完成后,我們確定范圍內的哪些數字(N - 長度,N] 是復合的,但我們不確定 rest 是素數還是復合數。因此,最后一步是對剩余的每個數字運行 Miller-Rabin 的素性檢驗數字。這些測試也是使用多線程方法完成的(僅適用於更大的整數)。

PrevPrime() 的計時在我的舊的慢速1.2 GHz CPU 上進行,它有 8 個硬件線程,不同位大小的整數有不同的計時:

32位 - 每一個0.55 () 小於0.00005 sec integer64位 - 0.0005 sec128位 - 0.002秒, 256位 - 0.012秒, 512位 - 0.07 - 位 - 0.1秒- 1.3秒。

所以你可以看到,在我的慢速 1.2 GHz CPU 1024 位 integer 上只需 1 秒即可輸出前一個素數。

上述時序不僅取決於整數的位大小,還取決於素數的密度。 眾所周知,對於位數為B的素數在ln(2^B)中平均出現一次。 因此更大的數字不僅對大整數有更困難的數學運算,而且還有更多稀有的素數,因此更大的數字在輸出前一個素數之前需要檢查更多的數字。

為了查看 PrevPrime() 中的篩選效率如何,我提供了 Fermat 和 Miller-Rabin 測試本身的時間(下面的F是 Fermat, MR是 Miller-Rabin):

128位 - 復合 MR 和 F - 0.0002秒,素數 MR - 0.0026秒 F - 0.0054秒; 256位 - 復合 MR 和 F - 0.0007秒,素數 MR - 0.0116秒 F - 0.0234秒; 512位 - 復合 MR & F - 0.0043秒,素數 MR - 0.0773秒 F - 0.1558秒; 1024位 - 復合 MR 和 F - 0.0290秒,素數 MR - 0.4518秒 F - 0.9085

從上面的統計數據我們可以得出結論,在針對相同的故障概率時,費馬測試比米勒拉賓花費的時間多兩倍。

正如你所看到的,對於 1024 位,單個 Miller-Rabin 的素數大約是 0.5 秒,而整個 PrevPrime() 運行在 1 秒內,這意味着 PrevPrime() 非常高效,它只比使用素數的單次測試慢兩倍米勒-拉賓。

當然,您可能會注意到我使用了概率素數測試,因為 1024 位數字確定性測試需要非常多的時間。

對於概率測試,我選擇的目標失敗概率等於 1/2^32 = 1/4294967296,因此素數測試失敗的情況非常罕見。 您可以選擇較小的概率,如果您需要更高的精度,它可以在我的程序中進行調整。

從理論上講,Fermat 的每個單一測試給出 1/2 的失敗機會(不包括幾乎 100% 的失敗機會的卡邁克爾數),而 Miller-Rabin 給出 1/4 的失敗機會。 因此,要達到目標概率 1/2^32,需要 32 步費馬和 16 步米勒拉賓。

每個位大小所需的素數閾值是可自動調整的,但需要大量的單次預計算,這就是我計算表並對其進行硬編碼的原因,但計算表的代碼仍然存在於我的代碼中。 If you need to re-compute thresholds then go to function ComputePrevPrimeOptimalPrimesCnt() and comment out first line (with return bits <= 32? 128: ..... ) and run program, it will output sizes optimal for your PC and bit -尺寸。 但我預先計算的表格應該適用於任何 CPU,只需要調整新的位大小。

如果您不知道我唯一依賴的庫是Boost 它可以通過sudo apt install libboost-dev-all輕松安裝在 Linux 下。 在 Windows 下有點困難,但仍然可以通過Chocolatey 的 Boost Package安裝(做choco install boost-msvc-14.3 ,但先安裝Chocolatey )。

默認情況下,當程序運行時,它會運行所有測試,測試是代碼中所有以宏TEST(TestName) {.....開頭的函數。 如果您需要額外的測試,只需編寫一個像TEST(NewTest) {...code here... }這樣的主體,不需要額外的測試注冊。 如果您確定在使用我的庫時也可以刪除所有 TEST() 函數,則不需要它們來運行,除非它們對徹底測試我的庫很有用。

重要通知!!! . 我在 1 天內編寫的這段代碼,所以它沒有經過很好的測試,並且可能在某處包含一些錯誤。 因此,如果不仔細查看代碼和/或進行額外測試,不建議直接在生產中使用它。 它更像是教育代碼,可以為您編寫自己的生產就緒代碼提供一種指導。

代碼如下。

在線嘗試!

注意Try it online!在線運行限制為 128 位(可能為 1024 位),由於 GodBolt 服務器將程序總運行時間限制為 3 秒,下載后將test_till_bits = 128更改為1024 )。

源代碼在這里 托管在Github Gist 源代碼上。 不幸的是,由於 StackOverflow 帖子限制為 30K 符號,我無法在此處內聯完整的源代碼,因為只有我的源代碼有 30K 字節,不包括上面的長英文描述(額外的 10K)。 因此,我在 Github GIST(上面的鏈接)和 GodBolt 服務器( Try it online!上面的鏈接)上共享源代碼,兩個鏈接都有完整的源代碼。

代碼控制台 output:

'Test GenPrimes' 0.006 sec
'Test GenPrimes_CntPrimes' 0.047 sec
'Test MillerRabin' 0.006 sec
Fermat_vs_MillerRabin: bits    8 prp_cnt  58 mr_time 0.0000|0.0000 f_time 0.0000|0.0000, total time 0.009 sec
Fermat_vs_MillerRabin: bits   16 prp_cnt  57 mr_time 0.0000|0.0000 f_time 0.0000|0.0000, total time 0.001 sec
Fermat_vs_MillerRabin: bits   32 prp_cnt  46 mr_time 0.0000|0.0000 f_time 0.0000|0.0000, total time 0.004 sec
Fermat_vs_MillerRabin: bits   64 prp_cnt  22 mr_time 0.0000|0.0005 f_time 0.0000|0.0011, total time 0.041 sec
Fermat_vs_MillerRabin: bits  128 prp_cnt  13 mr_time 0.0002|0.0028 f_time 0.0002|0.0056, total time 0.134 sec
Fermat_vs_MillerRabin: bits  256 prp_cnt   6 mr_time 0.0008|0.0120 f_time 0.0008|0.0245, total time 0.321 sec
Fermat_vs_MillerRabin: bits  512 prp_cnt   1 mr_time 0.0042|0.0618 f_time 0.0042|0.1242, total time 0.857 sec
Fermat_vs_MillerRabin: bits 1024 prp_cnt   1 mr_time 0.0273|0.4101 f_time 0.0273|0.8232, total time 3.807 sec
'Test IsProbablyPrime_Fermat_vs_MillerRabin' 5.176 sec
'Test PrevPrime_ReCheckWith_PrimesDiv_and_Fermat' 5.425 sec
PrevPrime    8-bit threads:1  0.0000 sec, avg distance    3.3, total time 0.001 sec
PrevPrime    8-bit threads:8  0.0000 sec, avg distance    3.3, total time 0.000 sec
PrevPrime   16-bit threads:1  0.0000 sec, avg distance   11.2, total time 0.000 sec
PrevPrime   16-bit threads:8  0.0000 sec, avg distance   11.2, total time 0.000 sec
PrevPrime   32-bit threads:1  0.0001 sec, avg distance   10.5, total time 0.001 sec
PrevPrime   32-bit threads:8  0.0001 sec, avg distance   10.5, total time 0.001 sec
PrevPrime   64-bit threads:1  0.0014 sec, avg distance   39.7, total time 0.025 sec
PrevPrime   64-bit threads:8  0.0012 sec, avg distance   39.7, total time 0.023 sec
PrevPrime  128-bit threads:1  0.0110 sec, avg distance   85.2, total time 0.170 sec
PrevPrime  128-bit threads:8  0.0084 sec, avg distance   85.2, total time 0.142 sec
PrevPrime  256-bit threads:1  0.0452 sec, avg distance  207.4, total time 0.570 sec
PrevPrime  256-bit threads:8  0.0331 sec, avg distance  207.4, total time 0.473 sec
PrevPrime  512-bit threads:1  0.1748 sec, avg distance  154.0, total time 2.246 sec
PrevPrime  512-bit threads:8  0.1429 sec, avg distance  154.0, total time 2.027 sec
PrevPrime 1024-bit threads:1  2.1249 sec, avg distance  379.4, total time 15.401 sec
PrevPrime 1024-bit threads:8  1.6030 sec, avg distance  379.4, total time 12.778 sec
'Test PrevPrime' 33.862 sec
'Program run time' 44.524 sec

看來您正在解決這個問題

正如@Ziyao Wei所說,您可以簡單地使用Miller-Rabin素數測試來解決它。

這是我的解決方案

#include<cstdio>
#include<cstdlib>
#include<ctime>

short T;
unsigned long long n;

inline unsigned long long multi_mod(const unsigned long long &a,unsigned long long b,const unsigned long long &n)
{
    unsigned long long exp(a%n),tmp(0);
    while(b)
    {
        if(b&1)
        {
            tmp+=exp;
            if(tmp>n)
                tmp-=n;
        }
        exp<<=1;
        if(exp>n)
            exp-=n;
        b>>=1;
    }
    return tmp;
}

inline unsigned long long exp_mod(unsigned long long a,unsigned long long b,const unsigned long long &c)
{
    unsigned long long tmp(1);
    while(b)
    {
        if(b&1)
            tmp=multi_mod(tmp,a,c);
        a=multi_mod(a,a,c);
        b>>=1;
    }
    return tmp;
}

inline bool miller_rabbin(const unsigned long long &n,short T)
{
    if(n==2)
        return true;
    if(n<2 || !(n&1))
        return false;
    unsigned long long a,u(n-1),x,y;
    short t(0),i;
    while(!(u&1))
    {
        ++t;
        u>>=1;
    }
    while(T--)
    {
        a=rand()%(n-1)+1;
        x=exp_mod(a,u,n);
        for(i=0;i<t;++i)
        {
            y=multi_mod(x,x,n);
            if(y==1 && x!=1 && x!=n-1)
                return false;
            x=y;
        }
        if(y!=1)
            return false;
    }
    return true;
}

int main()
{
    srand(time(NULL));
    scanf("%hd",&T);
    while(T--)
    {
        for(scanf("%llu",&n);!miller_rabbin(n,20);--n);
        printf("%llu\n",n);
    }
    return 0;
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM