#欧拉余数定理算法
对于 A^B mod C
(网上的算法基本上不见考虑 A ,C 不互质的情况,显然是残缺的)
1.如果 A ,C 不互质(互质:两个数做因式分解,公共因子只有1)
通过约分使得新的C1与A互质,如果C1与A不互质,则重复此步骤。
记录的约数积Y 和商S积
2 如果 A 大于 C ,则A = A %C
3 求C的欧拉函数值euler
4 如果B不小于euler, B=B%euler
5 计算 e=A^B ,此时A,B已经较小了
6 A^B mod C =(e*S %C)*Y
举个例子 :15 ^6mod12 = 9
-
A=15
-
B=6
-
C=12
一,15,12有约数3:
-
A=15
-
B=B-1=5
-
C=12/3=4
-
Y=3
-
S=15/3=5
二 A>C:
-
A=A%C=15%4=3
三 求C的欧拉函数值:
-
euler(4)=2
四 :
-
B=B%euler=5/2=1
五 计算 :
-
e=A^B =3 ^ 1=3
六 :
-
A^B mod C =(e*S %C )*Y = 3* 5%4*3=15%4*3=3*3=9
代码
public class Mod {
public static int powerMod(int a,int pow,int n) {
int factor=coprime(a,n);
int multiplier=1;
int quotient=1;
int cya=a;
int cyn=n;
int cypow=pow;
while(factor>0&&cypow>1) {
multiplier*=factor;
quotient*=cya/factor;
cyn/=factor;
cypow--;
factor=coprime(cya,cyn);
}
if(cya>cyn) {
cya%=cyn;
}
int euler = eulerFunction(cyn);
if(cypow>euler&&euler>0) {
cypow%=euler;
}
int ret=1;
if(cya==0) {
ret=0;
}else if(cypow==0) {
ret=1;
}
while(cypow>0) {
ret*=cya;
cypow--;
}
return (quotient*ret%cyn)*multiplier;
}
public static int eulerFunction(int n) {
if(isPrimeNumber(n)) {
return n-1;
}
int count=0;
for(int i=1;i<n;i++) {
if(coprime(i,n)<0) {
count+=1;
}
}
return count;
}
public static int coprime(int a,int b) {
int min = Mod.min(a,b);
for(int i=2;i<=min;i++) {
if(a%i==0) {
if(b%i==0) {
return i;
}
}
}
return -1;
}
public static <E extends Comparable<E>> E min(E ... pars) {
Objects.requireNonNull(pars);
if(pars.length==0) {
throw new NullPointerException();
}
E min=pars[0];
for(int i=1;i<pars.length;i++) {
if(min.compareTo(pars[i])>0) {
min=pars[i];
}
}
return min;
}
}
测试 欧拉函数
for(int i=1;i<21;i++) {
System.out.println(i+"->"+eulerFunction(i));
}
1->0
2->1
3->2
4->2
5->4
6->2
7->6
8->4
9->6
10->4
11->10
12->4
13->12
14->6
15->8
16->8
17->16
18->6
19->18
20->8
测试模方法
手工单个检验无疑是不可取得,所以使用BigInteger 进行结果检验。
A C 的值一定要取小点,原因在下文。
for(int i=1;i<10;i++) {
int a=(int) (Math.random()*20);
int b=(int) (Math.random()*1000+1);
int c=(int) (Math.random()*20+1);
System.out.println(a+" ^ "+b+" mod "+c);
System.out.print("powerMod "+powerMod(a,b,c));
BigInteger big=BigInteger.valueOf(a);
System.out.println(" vs " +" big "+big.modPow(BigInteger.valueOf(b), BigInteger.valueOf(c)));
}
13 ^ 214 mod 2
powerMod 1 vs big 1
12 ^ 178 mod 16
powerMod 0 vs big 0
6 ^ 73 mod 16
powerMod 0 vs big 0
3 ^ 890 mod 13
powerMod 9 vs big 9
6 ^ 572 mod 17
powerMod -5 vs big 13
8 ^ 805 mod 2
powerMod 0 vs big 0
15 ^ 665 mod 18
powerMod 9 vs big 9
10 ^ 290 mod 2
powerMod 0 vs big 0
17 ^ 149 mod 6
powerMod 5 vs big 5
很容易发现有个异常值:
6 ^ 572 mod 17
powerMod -5 vs big 13
不难想到原因定是整型溢出。
可以推敲一下。
euler(17)=16
572%16=12
6 ^ 12 = 3 ^ 24
int=2^31-1< 2 ^32
3 ^ 24 /2 ^ 32=(3/2) ^ 24 / (2 ^ 8 )
3/2=1.5>2 ^ (1/2)=1.414
(3/2) ^ 24 / (2 ^ 8 ) > ( 2 ^ (1/2) ) ^ 24 / (2 ^ 8 ) =2 ^ 12 / 2 ^8=16
至少大了16倍,所以必然出错。
解决方案:
将int 包装成 BigInteger 即可。
增加本算法与BigInteger.modPow()时间效率的对比,不禁感叹源码的神奇。