提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言:两种高效的求mod方法
一、(a*b)%n
代码如下:
public long multiple(long a,long b,long n){
long ans = 0;
while(b > 0){
if((b & 1) == 1){
ans = (ans+a)%n;
}
a = (a + a)%n;
b>>=1;
}
return ans;
}
其实主要就是把b化成二进制表示,遇到为1的位就对结果加a,否则丢弃当前的最低位。因为右移了一位,下一次加的a就变成2*a了,用加法会更快一些。但是为什么变成2a之后可以取模,是我不理解的地方。
二、(a*b)%n
代码如下:
//幂运算要借助到之前的乘法运算
public long power(long a,long b,long n){//b个a相乘,把个数b化成2的多项式的形式,
// 每次递归求出二项式的每一项,再通过位是否为1判断需不需要这一项
long ans = 1;
while(b > 0){
if((b & 1) == 1){
ans = multiple(ans,a,n);//(ans * a)mod n
}
a = multiple(a,a,n);//(a * a)mod n
b >>= 1;
}
return ans;
}
multiple(a,a,n)就是求多项式的某一项的值,multiple(ans,a,n)就是把这一项加到总和里。
Miller-Rabin测试
先摆三条定理
费马小定理:对于素数p和任意整数a,有a^p ≡ a (mod p)(同余)。
反过来,满足a^p ≡ a(mod p),p也几乎一定是素数。
伪素数:如果n是一个正整数,如果存在和n互素的正整数a满足 a^(n-1) ≡ 1(mod n),
我们说n是基于a的伪素数。如果一个数是伪素数,那么它几乎肯定是素数。
Miller-Rabin测试:不断选取不超过n-1的基b(s次),计算是否每次都有b^(n-1) ≡ 1(mod n),
若每次都成立则n是素数,否则为合数。
之所以说几乎,因为MIller-Rabin测试是概率型的,不是确定型的,不过由于多次运行后
出错的概率非常小,所以实际应用还是可行的。(一次Miller-Rabin测试其成功的概率为
3/4)。
再来一条定理
二次探测定理:如果p是奇素数,则 x^2 ≡ 1(mod p)的解为 x = 1 || x = p - 1(mod p);
代码如下:
public boolean miller_rabin(int n){
if(n==2 || n==3 || n==5 || n==7 || n==11) return true;
if(n==1 || (n%2)==0 || (n%3)==0 || (n%5)==0 || (n%7)==0 || (n%11)==0) return false;
long x,pre,u;
int offset=0;
u=n-1;
while((n&1) != 0){
offset++;
n>>=1;
}
//去掉最低位1后面的0,为什么?减少x^n-1的计算量
//x的32次方要算32次,而去掉0算再乘回去只要算5次
//那x的41次方呢?101001
Random random = new Random();
//不断选取不超过n-1的x(s次),计算是否每次都有x^(n-1) ≡ 1(mod n),
//若每次都成立则n是素数,否则为合数
for(int i=0;i<8;i++){
x = random.nextInt() % (n-2) + 2;//2到n的随机数
if(x % n == 0) continue;
x = power(x,u,n);//计算x的n-1次方
pre = x;//存下来
for(int j = 0; j < offset; ++j) { //把移位减掉的量补上,并在这地方加上二次探测
x = multiple(x, x, n);
//pre=x,x=x*x
if(x == 1 && pre != 1 && pre != n-1)
return false;
//二次探测定理,这里如果x = 1则pre 必须等于 1或 n-1,否则可以判断不是素数
pre = x;
}
if(x != 1) return false; //费马小定理
}
return true;
}