I think this is a pretty good idea - it will allow some interesting
comparisons.
Have you thought about what methods like add will look like?
Something like:
class JavaInteger implements MPInteger {
// What benefit does declaring BigInteger value as final
give?
š š š š private final BigInteger value;
š š š š public MPInteger add(MPInteger that) {
š š š š š š š š BigInteger thatValue = ((JavaInteger)that).value;
š š š š š š š š return new JavaInteger(this.value.add(thatValue));
š š š š }
}
class GMPInteger implements MPInteger {
š š š š private final GMP_BigInt value;
š š š š public MPInteger add(MPInteger that) {
š š š š š š š š GMP_BigInt thatValue = ((GMPInteger)that).value;
š š š š š š š š return new GMPInteger(this.value.add(thatValue));
š š š š }
}
My implementation is similar:
class MPBigInteger implements MPInteger {
private BigInteger bigInteger;
public MPBigInteger add(MPInteger number) {
return new MPBigInteger(bigInteger.add(((MPBigInteger)
number).bigInteger));
}
...
}
I also use a factory to create the classes that implement MPInteger.
MPIntegerFactory interface is omitted.
public class MPBigIntegerFactory implements MPIntegerFactory {
public MPInteger createMPInteger() {
return new MPBigInteger();
}
public MPInteger createMPInteger(String strRep) {
return new MPBigInteger(strRep);
}
public MPInteger createMPInteger(long longRep) {
return new MPBigInteger(longRep);
}
}
Now it is really easy to switch between implementations.
A small problem with this is that you can wite:
MPInteger a = new JavaInteger(17);
MPInteger b = new GMPInteger(23):
MPInteger c = a.add(b);
And the first you'll know about it is when you get a ClassCastException
when you run the program.
You can prevent this using generics. Like so:
interface MPInteger<T extends MPInteger> {
š š š š public T add(T that);
}
class JavaInteger implements MPInteger<JavaInteger> {
š š š š public JavaInteger add(JavaInteger that) {
š š š š š š š š return new JavaInteger(this.value.add(that.value));
š š š š }
}
etc
You'd then have to write:
MPInteger<JavaInteger> a = new JavaInteger(17);
MPInteger<GMPInteger> b = new GMPInteger(23):
MPInteger<?> c = a.add(b);
And the compiler won't let you do that - or any other variant which would
fail at runtime. Basically, it won't let you throw away the implementation
type.
It does make writing code which manipulates integers polymorphically more
painful, though:
public <T> MPInteger<T> multiplyAndAdd(MPInteger<T> a, MPInteger<T> b) {
š š š š return a.mul(b.add(a));
}
That's four additional <T>s that need typing.
I didn't know this technique before. Thanks!
I've also conducted some performance measurements. The measuring
function is:
static void testBed() {
final int WARMUP_REPEATS = 50000000;
final int REPEATS = 100000000;
String dividend = "556345433454";
String divisor = "454354";
BigInteger biDividend = new BigInteger(dividend);
BigInteger biDivisor = new BigInteger(divisor);
BigInteger biQuotient;
MPInteger mpbiDividend = MPIntegerFactory.createMPInteger
(dividend);
MPInteger mpbiDivisor = MPIntegerFactory.createMPInteger(divisor);
MPInteger mpbiQuotient;
// Warming up
for (int i = 0; i < WARMUP_REPEATS; i++) {
biQuotient = biDividend.divide(biDivisor);
}
for (int i = 0; i < WARMUP_REPEATS; i++) {
mpbiQuotient = mpbiDividend.divide(mpbiDivisor);
}
long startTime = System.currentTimeMillis();
for (int i = 0; i < REPEATS; i++) {
biQuotient = biDividend.divide(biDivisor);
}
long endTime = System.currentTimeMillis();
long biRunTime = endTime - startTime;
System.out.println("BigInteger's time = " + biRunTime + " ms.");
startTime = System.currentTimeMillis();
for (int i = 0; i < REPEATS; i++) {
mpbiQuotient = mpbiDividend.divide(mpbiDivisor);
}
endTime = System.currentTimeMillis();
long mpbiRunTime = endTime - startTime;
System.out.println("MPBigInteger's time = " + mpbiRunTime + "
ms.");
System.out.println("The overhead = " +
((((Long)(mpbiRunTime - biRunTime)).doubleValue() /
((Long)biRunTime).doubleValue()) * 100) + "%.");
}
Results (with -server option):
1. String dividend = "55634543";
String divisor = "4543";
BigInteger's time = 31198 ms.
MPBigInteger's time = 33318 ms.
The overhead = 6.7953%.
2. String dividend = "556345433454";
String divisor = "454354";
BigInteger's time = 48600 ms.
MPBigInteger's time = 51080 ms.
The overhead = 5.1029%.
3. String dividend = "556345433454653456";
String divisor = "454354234";
Here strange things begin.
1st run:
BigInteger's time = 52053 ms.
MPBigInteger's time = 76320 ms.
The overhead = 46.6198%.
2nd run:
BigInteger's time = 77994 ms.
MPBigInteger's time = 54372 ms.
The overhead = -30.2869%.
First of all, the overhead exists. Maybe JIT does not inline method
calls (probably because methods are virtual). Maybe there are other
reasons. Secondly, from the first two results it was natural to
suppose that the overhead would be decreasing. But the last ones
shattered this assumption. I think it has something to do with garbage
collection.