This homework has a programming part and a written part. The programming part should be submitted as usual by email. The written part must be handed in either to me, OR to the TA. [Please do not just stuff in our mail boxes without asking first]
For applications like these, java provides the class BigInteger, which has instance methods for such common operations as add, subtract, multiply and divide (this is integer division, leaving a quotient); and also operations like gcd and bitwise operations like and, or, xor and complement. It also provides such useful functions as left and right shifts; and constructors to get a BigInteger from an integer, and also to generate a random BigInteger with some specified number of bits; and even some random BigInteger which is "probably" prime. These are very useful in the cryptographic application mentioned above.
Remember, in order to represent something like an arbitrary sized integer, the internal representation is probably some kind of vector of bits. Thus each of the basic operations mentioned above are not primitive operations which the machine provides, but built up from bitwise operations. You could think of this as the software implementation of what your hardware does for regular integers.
Addition is fairly straightforward: You start at the least significant bit; add the bits together, calculate the output bit and the carry; and go on to the next bit. This algorithm is of the order Theta(n).
Multiplication is more complicated. However, the basic structure of the multiplication algorithm can be expressed in a recursive fashion by using a divide and conquer algorithm. This splits up the two operands in equal halves, recursively multiplying them and combining the results to get the final product.
Suppose you want to compute Z = XY
where X and Y are both 2n-bit
binary numbers. We can view X as follows:
X = (X1 . 2n) + X0, Y = (Y1 . 2n) + Y0where the X0, X1, Y0, Y1 are each n bit numbers. After multiplying out, we get the product as:
Z = (X1.Y1).22n + (X1.Y0 + X0.Y1).2n + (X0.Y0) = Z2 . 22n + Z1.2n + Z0 where Z0 = X0.Y0 Z1 = X1.Y0 + X0.Y1 Z2 = X1.Y1So, at each step there are 4 recursive calls to multiply two n-bit numbers. The multiplication of an integer by 2n is performed by shifting the bits n places to the left (so the time complexity is O(n)). The overhead to combine the results of these multiplications comes from performing some additions and the shifts, which are linear time. Thus, to find the asymptotic complexity of this algorithm has the following recurrence:
T(n) = 4 * T(n/2) + nThe solution to this, as you can do from the previous classes and exercises is:
T(n) = Theta(n2)However, one can do better than this by doing three recursive multiplications instead of four, as Karatsuba showed. Instead of calculating Z1 using the two obvious products (X1.Y0 and X0.Y1) and then adding them together, we first compute the intermediate product each step:
T1 = (X0 + X1).(Y0 + Y1)Then the product is:
Z1 = T1 - Z0 - Z2Once we have Z0, Z2, Z1 we can put them together to form Z in linear time. The basic recurrence is thus:
T(n) = 3 * T(n/2) + nwhose solution is
T(n) = Theta(nlg 3)as shown in one of the previous homework.
Download and read the following "abstract" java program,
mult.java.
It is "abstract" because we omit the
various bodies for methods and putting dummy
bodies where we could not. [But you can call
"javac" on it without any error.]
We want you to implement the Karatsuba
algorithm
for integer multiplication.
To write your own mult.java
program,
first remove all occurences of the
key word "abstract" from the sample program,
then fill in the appropriate code. Remove also any
dummy code. The main
method has
already been written for you.