本帖最后由 gmh5225 于 2015-1-2 09:21 编辑
用途:判断一个数是否是素数,为RSA公钥私钥 作基础
首先引入一下快速幂/积取模运算;
快速积取模:
原理:利用二进制减少运算的次数- #define ll __int64
- ll mulSpeed(ll a,ll b,ll n)
- {
- ll s=0;
- while (b)
- {
- if (b&1)
- {
- s=(s+a)%n;
- }
- a=(a+a)%n;
- b>>=1LL;
- }
- return s;
- }
复制代码
快速幂取模
- ll powSpeed(ll a,ll b,ll n)
- {
- ll s=1;
- while (b)
- {
- if (b&1)
- {
- s=mulSpeed(s,a,n);
- }
- a=mulSpeed(a,a,n);
- b>>=1LL;
- }
- return s;
- }
复制代码
费马小定理:
对于素数p和任意整数a 有a^p =a(mod p) 这里的等号是同余的意思,相反如果有a^p =a(mod p),那么这个p一般来说是素数。
伪素数:
如果n是一个正整数,存在一个正整数a与n互素,满足 a^(n-1)=1(mod n),则n是基于a的一个伪素数,一般来说这个伪素数是素数。
Fermat素数测试:
循环s次,a是一个随机正整数,取值范围在[2,n-1]中,如果循环s次中,每一次都能满足a^(n-1)=1(mod n),那么这个数是素数,否则不是
注意:一个数如果不是素数,那么他一定不满足 2^(n-1)=1(mod n)
代码如下:
测试17是否是素数
- int n=17,a=0,flag=1,s=10; //在这里s=10
- srand((ll)time(0));
- for (int i=0;i<s;++i)
- {
- a=rand()%(n-2)+2;
- if (powSpeed(a,n-1,n)!=1)
- {
- flag=0;
- break;
- }
- }
- if (flag)
- {
- printf("多半是素数\n");
- }
- else
- printf("不是素数");
复制代码
但是:Fermat素数测试是概率型的测试,成功率为3/4,如上面的例子也就是说17是否是素数的成功的概率是3/4。
为此引入二次探测定理
二次探测定理:
如果正整数p是奇素数(除了2的素数)x是小于p的正整数,且x^2==1(mod p) 那么x=1或x=p-1
证明:
相当于x^2-1整除p (x+1)(x-1)整除p x=1 x=-1 x=p-1 x=p+1 又因为x是小于p的正整数,所以
x=1 或x=p-1
证明完毕。
也就是说如果 x^2 %p==1,但是x!=1&&x!=p-1,这时候p就不是素数
举个例子:假设此时a=2,p=341 p-1=340可以分成 (2^170)^2 2^170可以分成(2^85)^2 ,直到拆不出2停止,这个时候这个计数是2,我们可以用二进制去计算这个计数,因为总是再分解2,2在二进制中是10,2^n在二进制中是1后面n个0,也就是说只要记录二进制从后往前有几个0,就知道有几个2,就知道是2的多少次方。
这样可以把这个数拆成 d*(2^k) 其中d是一个奇数(2都拆完了肯定是奇数),k是有多少个2的次数
只要满足 x^2%x==1 而且x=1或x=p-1就行,还是341这个例子,我们来模拟一下他的步骤
假设此时a=2,p=341,p-1=340 有2^170,2^85
先算x x=2^85%341 pre=x 把x先保存一份给pre 再算x^2 x^2=x*x%341 因为a*b%c=a%c*b%c
所以2^85%341*2^85%341=2^85*2^85%341
如果 x^2==1,就是满足 x^2%x==1 但是pre!=1&&pre=p-1 那么这个数字就不是素数
x=2^85%341=32 pre=x x^2=2^85%341*2^85%341=1 此时x^2=1但是pre!=1或340 所以他不是素数
倘若 pre==1或pre==340 那么把x^2存一份给pre,此时的pre=2^170%341 继续循环判断
代码如下:
- bool Miller_Rabin(ll n)
- {
- if(n==2||n==3||n==5||n==7||n==11||n==13||n==17||n==19)
- return true;
- if(n==1||!(n%2)||!(n%3)||!(n%5)||!(n%7)||!(n%11)||!(n%13)||!(n%17)||!(n%19))
- return false;
- //如果n不满足 2^(n-1) %n==1,那么他一定不是素数
- if (powSpeed(2,n-1,n)!=1)
- return false;
- ll u=n-1; //a^(n-1)%n 如果是1 ,极大可能是素数,否则不是素数
- ll k=0;//计算 二进制后面有多少个0,为了拆成 奇数×2^k
- while (!(u&1)) //为了拆成一个 2^k *d 其中d 是奇数,每次都可以提取一个2
- {
- ++k;
- u>>=1LL;
- }
- //进行s次FerMat探测 const int s =8
- // a^(n-1)%n 是否是1
- const int s=10;
- ll a=0,pre=0;
- srand(time(0));
- for (int i=0;i<s;++i)
- {
- a=rand()%(n-2)+2;//取[2,n-1]
- if (!(a%n))
- continue;//如果这个a随机出来能与a整除 取不到模为1了,继续循环
- a=powSpeed(a,u,n);//先算出x
- pre=a;//保存一份x
- for (int j=0;j<k;++j)//循环上面算出的2的多少次方的k的次数
- {
- a=mulSpeed(a,a,n);//这里算的是x^2
- if (a==1&&pre!=1&&pre!=n-1) //如果x^2 也就是满足 a^2=1(mod n) 但是x却不是1或p-1
- {
- return false;
- }
- pre=a;//继续保存一份 继续循环
- }
- }
- return true;
- }
复制代码
注意:Miller-Rabin素性测试也是概率型的,随机取k个数进行测试,失误率大概是4^(-k),但是这种成功率是可以让我们接受的,
而且这是目前测试素数最好的方法。
|