BigInteger enhancing

D

Dmitriy Melnik

By how much, exactly?

What percentage of overall processing time would that be, exactly?

I've not measured it. It's just my presumption that two methods are
invoked slower than one. Though the overhead may really be negligibly
small. Anyway I decided to give up BigInteger and look for faster
library.
 
L

Lew

Dmitriy said:
I've not measured it. It's just my presumption that two methods are
Exactly.

invoked slower than one. Though the overhead may really be negligibly
small.

Or nonexistent. It all depends.
 
A

Arne Vajhøj

Dmitriy said:
I've not measured it. It's just my presumption that two methods are
invoked slower than one. Though the overhead may really be negligibly
small.

Assuming big BigInteger's then the relative overhead should be
very small.

Arne
 
D

Dmitriy Melnik

If you are after factorization with quadratic sieve, then you will
probably want to have your own notion of "big integers", plausibly
something mutable instead of the immutable BigInteger. You may want
to look at:http://www.alpertron.com.ar/ECM.HTM

It features a Java applet which performs such factorization with
elliptic curves, but switching to quadratic sieve for big numbers.
The source code is available; it is quite ugly but it works. The
author apparently used his own code for big integers.

I have not implemented quadratic sieve, but I tried ECM, both with
BigInteger, and with some custom code where big integers are held in
arrays of int. The custom code was about twice faster, mostly because I
could keep the numbers in Montgomery representation, for faster modular
multiplications.
Thanks for the information!

What about BigInteger enhancing I have decided to create interface
MPInteger:

public interface MPInteger {
public MPInteger abs();
public MPInteger add(MPInteger number);
...
public MPInteger sqrt();
}

I am going to implement it in several classes, each of them using
different libraries. One with BigInteger delegation, one with GMP and
so on. I think this will allow for easy switching between different
implementations.
 
A

Arne Vajhøj

Dmitriy said:
What about BigInteger enhancing I have decided to create interface
MPInteger:

public interface MPInteger {
public MPInteger abs();
public MPInteger add(MPInteger number);
...
public MPInteger sqrt();
}

I am going to implement it in several classes, each of them using
different libraries. One with BigInteger delegation, one with GMP and
so on. I think this will allow for easy switching between different
implementations.

That is good OOP.

Arne
 
T

Tom Anderson

What about BigInteger enhancing I have decided to create interface
MPInteger:

public interface MPInteger {
public MPInteger abs();
public MPInteger add(MPInteger number);
...
public MPInteger sqrt();
}

I am going to implement it in several classes, each of them using
different libraries. One with BigInteger delegation, one with GMP and so
on. I think this will allow for easy switching between different
implementations.

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 {
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));
}
}

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.

tom
 
D

Dmitriy Melnik

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.
 
J

John B. Matthews

Dmitriy Melnik said:
// What benefit does declaring BigInteger value as final give?

In this case, value is initialized to null, and any subsequent
assignment is precluded. I took this as pseudo-code, suggesting some
definite value, fixed at construction. In general, the object
referenced by a final variable may be altered; but, in this example,
BigInteger is immutable:

<http://java.sun.com/docs/books/tutorial/java/javaOO/classvars.html>
<http://renaud.waldura.com/doc/java/final-keyword.shtml>

[...]
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.

You might also look at <http://jscience.org/>, particularly the
LargeInteger class:

<http://jscience.org/api/org/jscience/mathematics/number/LargeInteger.html>
 
J

John B. Matthews

Yeah, but they can't spell "Karatsuba": "Concurrent Karabutsa [sic]
multiplication" :)

Aha, the operation may be commutative, but not the letters of his name!
 
L

Lew

Tom said:
[...]
class JavaInteger implements MPInteger {
private final BigInteger value;

public MPInteger add(MPInteger that) {
BigInteger thatValue = ((JavaInteger)that).value;
return new JavaInteger(this.value.add(thatValue));
}
}
In this case, value is initialized to null, and any subsequent
assignment is precluded.

There was no explicit initialization of 'value', consequently the code would
not have compiled. Clearly John is right to call it "pseudo-code, suggesting
some definite value, fixed at construction".

Since 'Java' is a terrible name part for a Java class, I changed a couple of
identifiers in my test, but this is what I get with a suitable constructor added:

<sscce>
<source name="testit/MPInteger.java">
package testit;

public interface MPInteger
{
public MPInteger add( MPInteger that );
}
</source>

<source name="testit/Foonteger.java">
package testit;

import java.math.BigInteger;

public class Foonteger implements MPInteger
{
private final BigInteger value;

public Foonteger( BigInteger val )
{
this.value = val;
}

@Override
public MPInteger add( MPInteger that )
{
BigInteger thatValue = ((Foonteger) that).value;
return new Foonteger( this.value.add( thatValue ));
}
}
</source>
</sscce>

I have issues with the mandatory downcast. There is a risk of a
ClassCastException.
 
Joined
Nov 25, 2008
Messages
17
Reaction score
0
extend

You can write an Interface.
about this Interface ,define you own method,and other you like.
after this ,use BigInteger implements Interface..
 
T

Tom Anderson

Tom said:
[...]
class JavaInteger implements MPInteger {
private final BigInteger value;

public MPInteger add(MPInteger that) {
BigInteger thatValue = ((JavaInteger)that).value;
return new JavaInteger(this.value.add(thatValue));
}
}
In this case, value is initialized to null, and any subsequent assignment
is precluded.

There was no explicit initialization of 'value', consequently the code would
not have compiled. Clearly John is right to call it "pseudo-code, suggesting
some definite value, fixed at construction".

Well, or it could just be wrong.

Yes, i was leaving out details that weren't directly germane to the point
i was making, which sadly did mean that the class as written was bogus!

tom
 
J

John B. Matthews

Lew: Thank you for clarifying that. One nice feature of final instance
variables is that you have until the end of the constructor to initialize
them, while still ensuring the reference can't be changed:

Well, or it could just be wrong.

Tom: Perish the thought. :)

What about the more primitive approach, below? IIUC, the OP wants to
compare compare implementations, not use them interchangeably. First,
create wrappers with identical interfaces for each implementation,
using e.g. java.math.BigInteger and org.jscience.mathematics.number.
LargeInteger. Then just import the desired one:

<code>
<file name="MegaTest.java">
package cli;

import cli.big.MegaInteger;
//import cli.large.MegaInteger;

public class MegaTest {

public static void main(String[] args) {
MegaInteger m = MegaInteger.valueOf(2);
System.out.println(m.add(m));
System.out.println(m.subtract(m));
}
}
</file>

<file name="cli/big/MegaInteger.java">
package cli.big;

import java.math.BigInteger;

public class MegaInteger {

public static final MegaInteger ZERO = new MegaInteger();

private final BigInteger value;

private MegaInteger() {
this.value = BigInteger.valueOf(0);
}

private MegaInteger(BigInteger value) {
this.value = value;
}

public static MegaInteger valueOf(long value) {
return new MegaInteger(BigInteger.valueOf(value));
}

public MegaInteger add(MegaInteger that) {
BigInteger result = this.value.add(that.value);
return new MegaInteger(result);
}

public MegaInteger subtract(MegaInteger that) {
BigInteger result = this.value.subtract(that.value);
return new MegaInteger(result);
}

@Override public String toString() {
return value.toString();
}
}
</file>

<file name="cli/large/MegaInteger.java">
package cli.large;

import org.jscience.mathematics.number.LargeInteger;

public class MegaInteger {

public static final MegaInteger ZERO = new MegaInteger();

private final LargeInteger value;

private MegaInteger() {
this.value = LargeInteger.valueOf(0);
}

private MegaInteger(LargeInteger value) {
this.value = value;
}

public static MegaInteger valueOf(long value) {
return new MegaInteger(LargeInteger.valueOf(value));
}

public MegaInteger add(MegaInteger that) {
LargeInteger result = this.value.plus(that.value);
return new MegaInteger(result);
}

public MegaInteger subtract(MegaInteger that) {
LargeInteger result = this.value.minus(that.value);
return new MegaInteger(result);
}

@Override public String toString() {
return value.toString();
}
}
</file>
</code>
 
A

Arne Vajhøj

Tom said:
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 {
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));
}
}

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.

Sure.

Because it is very very bad OOP.

#public MPInteger add(MPInteger that) {

promises that it can handle any MPInteger

#BigInteger thatValue = ((JavaInteger)that).value;

violates that promise.
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.

That is good solution.

I would possible prefer an alternative with interfaces and
factories (JDBC and JAXP style).
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.

Typing is rarely the problem in the real world.

Arne
 
L

Lew

You evidently don't have the same keyboard as me!

The overhead of non-type-safe constructs that cause run-time bugs is
much higher than the 2.3 seconds it takes to type the extra <T>s. The
overhead of a maintenance programmer's confusion at why things don't
work because the underlying types are not documented by type
parameters is more valuable than the overhead of the original
programmer having to type them. Typing is the main physical skill of
the developer. If you think that's too hard, spend some time as a
roofer or a farmer, then come back to programming.

Sheesh!
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top