为了探究Java大数自带的modPow方法(大数快速幂取模)在ACM比赛中时间复杂度的可行性,我以 POJ 1995 Raising Modulo Numbers 进行测试,POJ的编译器是J2SE 1.5。

这是一个快速幂取模的题目,用long类型手写快速幂取模即可,但是BigInteger类有自带快速幂取模的modPow方法,可以直接调用,两者的运行时间会相差多少呢?

代码一,直接调用BigInteger类中的modPow方法,1454MS

import java.math.*;
import java.util.*;

public class Main {

    public static void main(String [] args) {
        Scanner cin=new Scanner(System.in);
        int T=cin.nextInt();
        while(T--!=0) {
            BigInteger p=cin.nextBigInteger();
            int m=cin.nextInt();
            BigInteger s=BigInteger.ZERO;
            while(m--!=0) {
                BigInteger a=cin.nextBigInteger();
                BigInteger b=cin.nextBigInteger();
                BigInteger t=a.modPow(b,p);//t=a^b%p
                s=s.add(t).mod(p);//s=(s+t)%p
            }
            System.out.println(s);
        }
    }
}

代码二,手写大数快速幂,参数含BigInteger类

①参数均为BigInteger类,1875MS
①参数中指数为long类型,底数和模数为BigInteger类,1454MS

import java.math.*;
import java.util.*;

public class Main {

    public static void main(String [] args) {
        Scanner cin=new Scanner(System.in);
        int T=cin.nextInt();
        while(T--!=0) {
            BigInteger p=cin.nextBigInteger();
            int m=cin.nextInt();
            BigInteger s=BigInteger.ZERO;
            while(m--!=0) {
                BigInteger a=cin.nextBigInteger();
                BigInteger b=cin.nextBigInteger();
                BigInteger t=qpow(a,b,p);
                s=s.add(t).mod(p);
            }
            System.out.println(s);
        }
    }

    static BigInteger qpow(BigInteger a,BigInteger b,BigInteger p) {//大数快速幂 求a^b%p 1875ms
        BigInteger s=BigInteger.ONE;//s=1
        BigInteger two=BigInteger.valueOf(2);
        while(!b.equals(BigInteger.ZERO)) {
            if((b.mod(two)).equals(BigInteger.ONE)) s=s.multiply(a).mod(p);
            a=a.multiply(a).mod(p);
            b=b.divide(two);
        }
        return s;
    }

    /*static BigInteger qpow(BigInteger a,long b,BigInteger p) {//快速幂求a^b%p 1454ms
        BigInteger s=BigInteger.ONE;//s=1
        while(b!=0) {
            if(b%2==1) s=s.multiply(a).mod(p);
            a=a.multiply(a).mod(p);
            b=b/2;
        }
        return s;
    }*/

}

代码三,手写快速幂,参数均为long类型,875MS

import java.math.*;
import java.util.*;

public class Main {

    public static void main(String [] args) {
        Scanner cin=new Scanner(System.in);
        int T=cin.nextInt();
        while(T--!=0) {
            long p=cin.nextLong();
            long m=cin.nextLong();
            long s=0;
            while(m--!=0) {
                long a=cin.nextLong();
                long b=cin.nextLong();
                s+=qpow(a,b,p);
                s%=p;
            }
            System.out.println(s);
        }
    }

    static long qpow(long a,long b,long p) {//快速幂求a^b%p
        long s=(long)1;//s=1
        while(b!=0) {
            if((b&1)==1) s=s*a%p;
            a=a*a%p;
            b=b/2;
        }
        return s;
    }
}

分析&&结论

从结果上看,已经很明显了,比较程序的运行效率:
手写long范围快速幂(875ms) > 调用modPow(1454ms) > 手写大数快速幂(指数为long范围 1454ms/三个参数均为大数 1875ms) 。

说明如果底数、指数、模数都没有超过long范围,手写快速幂最快;
如果需要用到大数,调用modPow比手写快。

BigInteger.class中的modPow方法源码

源码确实还是很强的(不愧是Java创始人写的),modPow方法要求三个参数均为BigInteger类,单从这题测试来看,如果要求手写的快速幂传入参数均为BigInteger类,那么源码的方法比手写快了400MS+,这还只是long范围的测试,如果全是大数运算的快速幂,时间差距应该会更大。

源码没太看懂,应该是用数学方法优化了吧,有modInverse求逆元,还有中国剩余定理等等(Combine results using Chinese Remainder Theorem)。

/**
 * Returns a BigInteger whose value is
 * <tt>(this<sup>exponent</sup> mod m)</tt>.  (Unlike {@code pow}, this
 * method permits negative exponents.)
 *
 * @param  exponent the exponent.
 * @param  m the modulus.
 * @return <tt>this<sup>exponent</sup> mod m</tt>
 * @throws ArithmeticException {@code m} &le; 0 or the exponent is
 *         negative and this BigInteger is not <i>relatively
 *         prime</i> to {@code m}.
 * @see    #modInverse
 */
public BigInteger modPow(BigInteger exponent, BigInteger m) {
    if (m.signum <= 0)
        throw new ArithmeticException("BigInteger: modulus not positive");

    // Trivial cases
    if (exponent.signum == 0)
        return (m.equals(ONE) ? ZERO : ONE);

    if (this.equals(ONE))
        return (m.equals(ONE) ? ZERO : ONE);

    if (this.equals(ZERO) && exponent.signum >= 0)
        return ZERO;

    if (this.equals(negConst[1]) && (!exponent.testBit(0)))
        return (m.equals(ONE) ? ZERO : ONE);

    boolean invertResult;
    if ((invertResult = (exponent.signum < 0)))
        exponent = exponent.negate();

    BigInteger base = (this.signum < 0 || this.compareTo(m) >= 0
                       ? this.mod(m) : this);
    BigInteger result;
    if (m.testBit(0)) { // odd modulus
        result = base.oddModPow(exponent, m);
    } else {
        /*
         * Even modulus.  Tear it into an "odd part" (m1) and power of two
         * (m2), exponentiate mod m1, manually exponentiate mod m2, and
         * use Chinese Remainder Theorem to combine results.
         */

        // Tear m apart into odd part (m1) and power of 2 (m2)
        int p = m.getLowestSetBit();   // Max pow of 2 that divides m

        BigInteger m1 = m.shiftRight(p);  // m/2**p    //shiftRight 右移
        BigInteger m2 = ONE.shiftLeft(p); // 2**p      //shiftLight 左移

        // Calculate new base from m1
        BigInteger base2 = (this.signum < 0 || this.compareTo(m1) >= 0
                            ? this.mod(m1) : this);

        // Caculate (base ** exponent) mod m1.
        BigInteger a1 = (m1.equals(ONE) ? ZERO :
                         base2.oddModPow(exponent, m1));

        // Calculate (this ** exponent) mod m2
        BigInteger a2 = base.modPow2(exponent, p);

        // Combine results using Chinese Remainder Theorem
        BigInteger y1 = m2.modInverse(m1);//逆元
        BigInteger y2 = m1.modInverse(m2);

        if (m.mag.length < MAX_MAG_LENGTH / 2) {
            result = a1.multiply(m2).multiply(y1).add(a2.multiply(m1).multiply(y2)).mod(m);
        } else {
            MutableBigInteger t1 = new MutableBigInteger();
            new MutableBigInteger(a1.multiply(m2)).multiply(new MutableBigInteger(y1), t1);
            MutableBigInteger t2 = new MutableBigInteger();
            new MutableBigInteger(a2.multiply(m1)).multiply(new MutableBigInteger(y2), t2);
            t1.add(t2);
            MutableBigInteger q = new MutableBigInteger();
            result = t1.divide(new MutableBigInteger(m), q).toBigInteger();
        }
    }

    return (invertResult ? result.modInverse(m) : result);
}

~~希望能有大神能给我解答一下源码到底是怎么实现大数快速幂取模的~~