Eratosthenes 篩選求質數

December 2, 2021

質數(prime number)是大於 1 的自然數(目前數學上公認 1 不是質數,因為 1 只有一個因數,也就是 1 本身),除了自身之外,無法被其他整數整除的數,至於非質數的自然數稱為合(成)數(Composite number)。

質數沒有公式可求,如何快速地求出質數則一直是程式設計人員與數學家努力的課題,在這邊介紹著名的 Eratosthenes 求質數方法。

解法思路

將指定的數除以所有小於它的數,若可以整除就不是質數,然而如何減少迴圈的檢查次數?

假設要檢查的數是 N,小於 N 的數只要檢查至 N 的開根號就可以了,道理很簡單,假設 A * B = N,如果 A 大於 N 的開根號,在小於 A 之前的檢查,就可以先檢查到 B 這個數可以整除 N。不過在程式中使用開根號會精確度的問題,可以使用 i * i <= N 進行檢查,且執行更快。

再來假設有一個篩子存放 1 到 N,例如:

2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 …….. N

先將 2 的倍數篩去:

2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 …….. N

接著要篩去 3 的倍數,此時留下的數中,小於 3 的就是質數:

2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 …….. N

再來篩去 5 的倍數,此時留下的數中,小於 5 的就是質數,依序地將 7、11 … 的倍數篩去,最後留下的數就是質數,這就是 Eratosthenes 篩選(Sieve of Eratosthenes)。

檢查的次數還可以再減少,事實上,只要檢查 6n + 1 與 6n + 5 就可以了,也就是 2 與 3 不用檢查,後續數字檢查直接跳過 2 與 3 的倍數。

自古至今對於質數,數學家有著許多的研究,其中值得留意的是,歐幾里得提出有些質數可以寫成 2ᵖ - 1 的形式,馬蘭·梅森 針對 p 為質數且 p <= 257,2ᵖ - 1 是個質數列出了梅森質數(Mersenne prime),為了紀念他,2ᵖ - 1 形式的數稱為梅森數(Mersenne number)。

除了求質數之外,還有另一個面向是給定一個數,判定它是否為質數,如果該數是個很大的數,會是個艱難的任務,因為不存在快速、有效率的分解方法,退而求其次地,存在一些非確定性的質數測試方式。

例如費馬小定理,若 p 為質數,與 p 互質的數 a,則 aᵖ⁻¹ ≡ 1 (mod p)。

a ≡ b (mod N) 代表同餘式,也就是以 N 為模數,a mod N 與 b mod N 得到的餘數恒等;費馬小定理說的就是,若 p 為質數,與 p 互質的數 a,aᵖ⁻¹ mod p 會餘 1,相對地,若有個數 n,與 n 互質的數 a,aⁿ⁻¹ mod n 不餘 1,n 不是質數。

使用費馬小定理時,要注意的是,若 p 為質數,與 p 互質的數 a,aᵖ⁻¹ ≡ 1 (mod p) 成立,但不表示有個數 n,與 n 互質的數 a,滿足 aⁿ⁻¹ ≡ 1 (mod n),n 就是質數,例如 3215031751 並不是質數,然而可以找到 a 為 2、3、5、7 滿足 aⁿ⁻¹ ≡ 1 (mod n)。

Miller–Rabin primality test 基於費馬小定理加以改良,是不少程式庫提供的測試函式的實現原理(例如 Java 的 BigInteger 提供的 isProbablePrime 等方法),通過 Miller–Rabin 測試的數是質數的機率,比費馬小定理高許多(isProbablePrime 可以指定 certainty,傳回數是質數的機率是 1 - 1/2ᶜᵉʳᵗᵃⁱⁿᵗʸ),雖然通過測試並不保證是質數,不過這類測試的另一個價值是,未通過測試的一定不是質數。

長久以來,質數僅僅只是數學家的研究對象,直到 Ron Rivest、Adi Shamir、Leonard Adleman 提出了 RSA 加密演算出現,人們才意識到質數的重要性(以及商機),RSA 的出發點簡單來說,找出更大的質數很艱難,判定很大的數是否為質數也很難,如果兩個很大的質數相乘得到一個數,若只知道這個數,想反過來求出這個數是哪兩個質數相乘,更是一大數學難題。

雖然理論上可以透過一些演算加上暴力法破解,不過需要的運算資源與時間成本過高而不可行,說是這麼說,演算法中兩個質數的選擇還必須滿足一些條件(例如,最基本的不能選 2,畢竟相乘就會是偶數,一看就知道其 中一個質因數是 2),也沒有理論能證明,破解 RSA 的難度等價於質因數分解,也就是說,目前 RSA 的安全性建立在實際經驗,也就是目前尚未找到有效的演算可以破解 RSA,至於運算力更為的強大量子電腦呢?目前量子電腦尚未成熟,未來尚不可知,也許會演變成超大質數與量子電腦速度間的較勁?

程式實作

#include <stdio.h> 
#include <stdlib.h> 

#define N 1000 

void create(int*);
void filter(int*, int);

int main(void) { 
    int primes[N + 1] = {0};
    create(primes);
    
    int i;
    for(i = 2; i < N; i++) if(primes[i]) { 
        printf("%4d", i); 
    }
 
    return 0; 
} 

void create(int* primes) {
    primes[2] = primes[3] = primes[5] = 1;
    
    int i;
    for(i = 1;6 * i + 5 <= N; i++) {
        primes[6 * i + 1] = primes[6 * i + 5] = 1; 
    }
    if(6 * i + 1 <= N) { primes[6 * i + 1] = 1; }
    
    int n;
    for(n = 0;(6 * n + 5) * (6 * n + 5) <= N; n++) {
        filter(primes, 6 * n + 1);
        filter(primes, 6 * n + 5);
    }     
    if((6 * n + 1) * (6 * n + 1) <= N) { filter(primes, 6 * n + 1); }  
}

void filter(int* primes, int i) {
    if(primes[i]) { 
        int j;
        for(j = 2; j * i <= N; j++) {
            primes[j * i] = 0; 
        }
    }
}
import java.util.*;

public class Prime {
    public static List<Integer> create(int max) {
        LinkedList<Integer> primes = new LinkedList<>();
        primes.add(2); primes.add(3); primes.add(5);
        for(int i = 1; i <= (max - 5) / 6; i++) {
            primes.add(6 * i + 1); primes.add(6 * i + 5);
        }
        if(primes.getLast() + 2 <= max) {
            primes.add(primes.getLast() + 2);
        }

        for(int i = 2; i < primes.size(); i++) {
            Integer p = primes.get(i);
            for(int j = 2; j * p <= primes.getLast(); j++) {
                primes.remove(Integer.valueOf(j * p));
            }
        }
        return primes;
    }
    
    public static void main(String[] args) {
        for(Integer p : create(1000)) {
            System.out.printf("%4d", p);
        }
    }
}
def flatten(list):
    return [item for subList in list for item in subList]

def iterate(counter, primes):
    if counter < len(primes):
        newps = primes[0:counter + 1] + list(
          filter(lambda p: p % primes[counter] != 0, primes[counter + 1:]))
        return iterate(counter + 1, newps)
    else:
        return primes
    
def create(max):
    comp = flatten(
        [[6 * i + 1, 6 * i + 5] for i in range(1, (max - 5) // 6 + 1)])
    primes = [2, 3, 5] + comp + \
        ([comp[-1] + 2] if comp[-1] + 2 <= max else [])
    return iterate(2, primes)
    
for p in create(1000):
    print("%4d" % p, end="")
def create(max: Int) = {
    val comp = (for(i <- 1 to (max - 5) / 6) 
            yield List(6 * i + 1, 6 * i + 5)).toList.flatten
    val primes = List(2, 3, 5) ++ comp ++ 
            (if(comp.last + 2 <= max) List(comp.last + 2) else Nil)
    iterate(2, primes)
}

def iterate(counter: Int, primes: List[Int]): List[Int] = {
    if(counter < primes.size) {
        val newps = primes.take(counter + 1) ++ 
              primes.drop(counter + 1).filter(p => p % primes(counter) != 0)
        iterate(counter + 1, newps)
    } else  primes
}

create(1000).foreach(printf("%4d", _))
def iterate(counter, primes)
    if counter < primes.size
        newps = primes.take(counter + 1) + primes.drop(counter + 1)
                    .select { |p| p % primes[counter] != 0}
        iterate(counter + 1, newps)
    else
        primes
    end
end
    
def create(max)
    comp = (1..(max - 5) / 6).map {|i| [6 * i + 1, 6 * i + 5] }.flatten
    primes = [2, 3, 5] + comp + (comp[-1] + 2 <= max ? [comp[-1] + 2] : [])
    iterate(2, primes)
end
    
create(1000).each do |p|
    printf("%4d", p)
end
Array.prototype.reduce = function(init, f) {
    var value = init;
    for(var i = 0; i < this.length; i++) {
        value = f(value, this[i]);
    }
    return value;
};

function range(from, to) {
    var r = [];
    for(var c = 0, i = from; i < to; c++, i++) { r[i] = i; } 
    return r;
}

function iterate(counter, primes) {
    if(counter < primes.length) {
        var newps = primes.slice(0, counter + 1).concat(
                primes.slice(counter + 1, primes.length)
                      .filter(function(p) { 
                          return p % primes[counter] != 0; 
                      }));
        return iterate(counter + 1, newps);
    } else {
        return primes;
    }
}

function create(max) {
    var comp = range(1, parseInt((max - 5) / 6) + 1).map(function(i) {
        return [6 * i + 1, 6 * i + 5];
    }).reduce([], function(ac, list) {
        return ac.concat(list);
    });
    var last = comp.pop();
    var primes = [2, 3, 5].concat(
            comp, last + 2 <= max ? [last, last + 2] : [last]);
    return iterate(2, primes);
}

print(create(1000).join(' '));
flatten list = [item | subList <- list, item <- subList]

iter counter primes = 
    if counter < length primes then
        let newps = take (counter + 1) primes ++ 
                (filter (\p -> p `mod` (primes !! counter) /= 0) $ 
                     drop (counter + 1) primes)
        in iter (counter + 1) newps
    else primes
    
create max = iter 2 primes
    where comp = flatten [[6 * i + 1, 6 * i + 5] | i <- [1..(max - 5) `div` 6]]
          primes = [2, 3, 5] ++ comp ++ 
              if last comp + 2 <= max then [last comp + 2] else []

main = print $ create 1000
from '/lib/math' import pow
N = 1000

def sieve(primes, i) {
    if primes.get(i) {
        iterate(2, j -> j * i <= N).forEach(j -> primes.set(j * i, 0)) 
    }
}

def sixPlus1Or5(primes) {
    r = range(1, i -> 6 * i + 5 <= N)
    r.forEach(i -> primes.set(6 * i + 1, 1))
    r.forEach(i -> primes.set(6 * i + 5, 1))
    if 6 * r.length() + 1 <= N { 
        primes.set(6 * r.length() + 1, 1)
    }
}

def sieveSixPlus1Or5(primes) {
    r = range(0, i -> pow(6 * i + 5, 2) <= N)
    r.forEach(i -> sieve(primes, 6 * i + 1))
    r.forEach(i -> sieve(primes, 6 * i + 5))
    n = r.length()
    if pow(6 * n + 1, 2) <= N { 
        sieve(primes, 6 * n + 1)
    }  
}

def create() {
    primes = List.create(N + 1, 0)
    primes.set(2, 1)
    primes.set(3, 1) 
    primes.set(5, 1)

    sixPlus1Or5(primes)
    sieveSixPlus1Or5(primes)

    return primes
}

primes = create()
println(iterate(2, N).select(i -> primes.get(i)))

分享到 LinkedIn 分享到 Facebook 分享到 Twitter