gmh5225 发表于 2015-1-2 02:10

【算法】Miller_Rabin素数测试

本帖最后由 gmh5225 于 2015-1-2 09:21 编辑

用途:判断一个数是否是素数,为RSA公钥私钥 作基础


首先引入一下快速幂/积取模运算;

快速积取模:
原理:利用二进制减少运算的次数#definell __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是一个随机正整数,取值范围在中,如果循环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)^22^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

先算xx=2^85%341pre=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;//取
                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),但是这种成功率是可以让我们接受的,
而且这是目前测试素数最好的方法。



Shark恒 发表于 2015-1-2 02:16

这有很大用处啊。5225就是流弊~

520Kelly 发表于 2015-1-2 11:39

其实素数的定义我都还给老师了

wjc296335187 发表于 2015-1-2 13:08

好!很好!非常好

小者 发表于 2015-1-2 13:53

这是什么东东看来我的研究下

Scar-疤痕 发表于 2015-1-2 18:28

哈哈,学习下,好流弊啊

lucious 发表于 2015-1-5 02:07

我数论学的少,不要骗我
页: [1]
查看完整版本: 【算法】Miller_Rabin素数测试