问题
编写Java代码,求 1001803 mod 137 的值。
分析
求余运算是Java的基本功能,可以直接使用 (int)Math.power(a,b) % p
进行计算,但是由于指数运算增长太快,非常容易就超出 Integer 的数据类型,即便我们使用 Long,其最大值也不过是 264-1,约为 1.84X1019,远远小于 1001803,所以显然是无法直接求的。
为了解决问题,我们首先分析一下余数的性质。根据余数 性质3: 给定 且 ,则。比如,(7 9) mod 5 = 3,而((7 mod 5) (9 mod 5)) mod 5 = (2 4) mod 5 = 3。
根据这个性质,我们可以把问题变为1001对137取余后的乘积再余,即:
实现
根据以上分析,我们可以根据这个思路编写以下代码:
public static void main(String[] args){
int base = 1001;
int x = 803;
int p = 137;
int result = 1;
for (int i = 0; i < x; i++)
result *= base % p;
result %= p;
System.out.println("Result = " + result);
}
然而在执行时,仍然结果不正确,会报数据溢出的错误。经检查,发现溢出错误是发生在循环中的 result *= base % p
语句,即 result
仍然会超出数据范围,原因应该是多次乘法的值过大导致的,所以我们代码可以再优化一下,最终结果如下:
public static void main(String[] args){
int base = 1001;
int x = 803;
int p = 137;
int result = 1;
for (int i = 0; i < x; i++){
// 防溢出,一定要写在 result *= base % p; 的上面,读者可以思考下这是为什么。
if (result > 46340) // 注1
result %= p;
result *= base % p;
}
result %= p;
System.out.println("Result = " + result);
}
注1:在程序中进行乘法运算时,两个数的乘积可能会超出其数据类型所能表示的范围,所以此处的取到此类型所能表示的最大值开根的值。当前类型为int,其最大的正值为 2^31-1
,所以可选的最大值为 (int) Math.sqrt(2^31 - 1)
,即46,340。即底能取到最大值为 46,340,如果想取更大的值可以使用 long,计算范围的方法一样,其值为3,037,000,499。
运行计算后,结果为:
Result = 104
扩展
我们可以进一步对其进行扩展,将这个计算抽象出一个函数,以方便利用,其定义如下:
/**
* Return base^x % p.
* @param base The base of the exponential function.
* @param x A variable of the exponential function.
* @param p The divider.
* @return
*/
public static int getMod(int base, int x, int p) {
int r = 1;
for (int i = 0; i < x; i++){
if (result > 46340)
result %= p;
result *= base % p;
}
return r % p;
}