前言
根据《信息安全数学基础》课程内容第五章 素数与素数测试的学习,列出了三种判断整数是否是素数的算法。
习题:判断111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111131111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111是否是素数。
一、试除法
简单的方法–试除法(trial division),
代码如下:
1 bool IsPrime(int n){
2 for(int i = 2; i * i <= n; i++){
3 if(n % i == 0) return false;
4 }
5 return n != 1
6 }
#include <iostream>
using namespace std;
bool IsPrime(long long n) {
for (long long i = 2; i * i <= n; i ++) {
if (n % i == 0 ) {
return false;
}
}
return true;
}
int main() {
long long number_to_test = 111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111131111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111;
bool result = IsPrime(number_to_test);
cout << "该整数是" << endl;
cout << (result ? "素数" : "合数") << endl;
return 0;
}
结果由于测试整数太大无法运行
改进:
#include <iostream>
#include <string>
using namespace std;
string addStrings(const string& num1, const string& num2) {
string result;
int carry = 0;
int i = num1.size() - 1;
int j = num2.size() - 1;
while (i >= 0 || j >= 0 || carry) {
int sum = carry;
if (i >= 0) sum += num1[i] - '0';
if (j >= 0) sum += num2[j] - '0';
result.push_back('0' + (sum % 10));
carry = sum / 10;
if (i >= 0) i--;
if (j >= 0) j--;
}
reverse(result.begin(), result.end());
return result;
}
string multiplyStrings(const string& num1, const string& num2) {
if (num1 == "0" || num2 == "0") return "0";
string result(num1.size() + num2.size(), '0');
for (int i = num1.size() - 1; i >= 0; i--) {
int carry = 0;
for (int j = num2.size() - 1; j >= 0; j--) {
int product = (num1[i] - '0') * (num2[j] - '0') + (result[i + j + 1] - '0') + carry;
carry = product / 10;
result[i + j + 1] = '0' + (product % 10);
}
result[i] += carry;
}
if (result[0] == '0') result.erase(result.begin());
return result;
}
bool isPrime(const string& num_str) {
string sqrt_str = "1";
int sqrt_len = (num_str.size() + 1) / 2;
for (int i = 0; i < sqrt_len; i++) {
sqrt_str.push_back('0');
}
while (multiplyStrings(sqrt_str, sqrt_str) <= num_str) {
sqrt_str = addStrings(sqrt_str, "1");
}
long long num_sqrt = stoll(sqrt_str);
for (long long i = 2; i <= num_sqrt; i++) {
if (num_str[0] != '0' && stoll(num_str) % i == 0) {
return false;
}
}
return true;
}
int main() {
string num_str = "111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111131111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111";
string squared = multiplyStrings(num_str, num_str);
string cubed = multiplyStrings(squared, num_str);
bool result = isPrime(num_str);
cout << "The number is " << (result ? "prime" : "composite") << endl;
return 0;
}
将大整数存储为字符串,然后使用boost库来进行运算,但是我没有安装boost库,只能手动实现基本的大整数加法、乘法。中间又去学习了大整数知识。程序能够运行,但是涉及到非常大的整数运算和素性检测,计算时间超级长,没等到结果。可以再试试分段计算和boost库。
二、米勒测试
高效的素性判定算法:费尔马小定理。
费尔马小定理
如果 p 是素数,对任意 a ∈ Z 且 p ∤ a,以下等式成立:ap-1≡ 1 (mod p)
米勒测试基于费马小定理推广而来,利用上述性质,可以设计素数判定算法:多次随机选择a,检验上式是否成立。如果不成立,则p是合数;如果成立;p可能是素数。
需要注意的是:还存在伪素数,特别是 Carmichael 数的问题,它们也可能使等式成立。
米勒测试的思路:
设 n > 2 是一个奇素数,则 n − 1 可表达为:n − 1 = 2kq
其中,q 是一个奇数,k 是某个正整数。设 a 是任意选取的整数,且 gcd(a, n) = 1。
如果以下两个条件其中之一得到满足:
- aq ≡ 1 (mod n)
- 存在某个 0 ≤ j ≤ k − 1,使得 a2jq ≡ −1 (mod n)
则称 n 通过了以 a 为基的米勒测试。
米勒测试定理
设 n 是奇素数,对任意满足 gcd(a, n) = 1 的整数 a,n 必然会通过以 a 为基的米勒测试。
米勒测试存在误判可,但通过重复测试可以使误判概率极小。它避免了仅仅检验a(n-1)≡1(mod p)的方法可能发生的判错。可以看作是费马小定理方法的改进。
#Input:a是随机选取的基,n是要测试的整数,而q是整数满足n-1=2^k q
#Output:如果n是合数,输出0;否则n可能是素数,输出1,即通过米勒测试
def MillerTest(a, q, n):
x = pow(a, q, n)
if (x == 1) or (x == (n - 1)):
return 1
while q != (n - 1);
x = (x * x) % n
q *= 2
if x == (n - 1):
return 1
return 0
现在来判定一下111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111131111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
结果:是素数
三、Miller-Rabin算法
Miller-Rabin算法是建立在米勒测试基础上的改进随机化素数判定算法。相较于米勒测试:
- 改进了判定流程,增加了随机性
米勒测试是针对一个给定基a进行判定。Miller-Rabin算法中,每次选择一个随机整数a作为基,重复执行多轮判定。labin - 引入错误概率分析
拉宾测试给出了合数通过米勒测试的概率上界。Miller-Rabin算法利用此结果,通过重复判定控制总的错误概率。 - 减小了误判概率
对同一合数,Miller-Rabin算法误判概率比米勒测试小得多,更可靠。
Miller-Rabin算法的基本思路:
- 分解n-1的形式为2^r*q,其中d是一个奇数,r是非负整数。
- 重复选择不同的a进行多轮测试
- 如果任何一次失败,n是合数;如果所有轮都通过,n很可能是素数
- 通过重复轮数控制错误概率
#Input:整数n和米勒测试的次数
#Output:如果n通过k次米勒测试,输出1,否则输出0
def IsPrime(n, k):
q = n - 1
while is_even(p):
q /= 2
for i in range(k):
a = randint(2, n - 1)
if not MillerTest(a, q, n):
return 0
return 1
总结
知识点:三种判定素数的方法
复盘:将这几种方法从理论运用到实践的过程中还是出现了很多问题,在解决问题的过程中也学了很多其他东西。