# CMPSCI 311: Theory of Algorithms

### Multiplying Large Integers (Levitin 4.5)

Questions in black, solutions in blue

Java provides a `BigInteger` class for storing integers too long for an `int` or `long`. Here we will look at the complexity of addition and multiplication of `BigInteger` objects, and determine whether the unusual multiplication algorithm of Levitin section 4.5 is useful in this context.

We'll assume that a (non-negative) `BigInteger` is written in the form "the sum for i from 0 to n of biri, where r is a base such as `MAXINT+1`. Each bi is kept in an `int` variable, and these `int` variables are kept in some sort of list. We can get bi from b, for example, in O(1) time by the call `b.getTerm(i)`, and change bi to x in O(1) time by `b.setTerm(i,x)`.

• Question 1: Here are my sketched versions of two methods from the `BigInteger` class:

``````
{// returns sum of this and c
int n = max(this.size(), c.size());
BigInteger result = new BigInteger(n+1);
int carryBit = 0;
for (int i=0; i < n; i++) {
result.setTerm(i, this.getTerm(i) + c.getTerm(i) + carryBit);
result.setTerm(n, carryBit);
return result;}

BigInteger multiply (int x)
{// returns this times x
int n = this.size();
BigInteger result = new BigInteger(n+1);
int carryInt = 0;
for (int i=0; i < n; i++) {
result.setTerm(i, this.getTerm(i) * x + carryInt);
carryInt = multCarry(this.getTerm(i), x, carryInt);}
result.setTerm(n,carryInt);
return result;}
``````

Argue that each of these methods takes \$O(n)\$ time.

Each method has one loop that executes exactly n times. Inside that loop we have basic `int` operations plus methods that are given to take O(1) time. (The methods `addCarry` and ``` multCarry``` operate on individual `int` arguments.) So each of the loops takes O(n) time. The code outside the loops in each method also takes O(1) time except possibly for the constructor call, which might take as much as O(n) depending on the implementation of the class. But in any case the total time is O(n).

• Question 2: Write Java pseudocode using the `add` and `multiply` methods above to implement `BigInteger multiply (BigInteger c)` using the ordinary algorithm for multiplication. Analyze the running time.

``````         BigInteger multiply (BigInteger c)
{//returns this times c
int n = this.size();
int m = c.size();
BigInteger(result) = new BigInteger(n+m); //starts as 0
for (int i=0; i < m; i++)
return result;}
``````

The time is dominated by the loop, which runs m times. The inside of the loop has an `add`, a `times` with an ``` int```, and a `shift`, each of which takes O(n+m) time. So the entire time is O((n+m)2), or quadratic in the input size.

• Question 3: Write Java pseudocode to reimplement `multiply` utilizing the trick from section 4.5, where it is observed that if a = a1rn/2 + a0 and b = b1rn/2 + b0, then ab = (a1b1)rn + ((a1 + a0)(b1 + b0) - a1b1 - a0b0)rn/2 + (a0b0).

Assume you also have a method `shift` in the `BigInteger` class, taking an `int` argument, such that `b.shift(i)` is b times ri if i is positive and b divided by ri (with no remainder) if i is negative. There is also a method `tail` such that `b.tail(i)` gives the remainder when b is divided by ri. Each of these methods takes Θ(i) time.

``````         BigInteger multiply (BigInteger c)
{//returns this times c, using recursive multiplication
int n = max(this.size(), c.size());
BigInteger(result) = new BigInteger(2*n); //starts as 0
BigInteger a1 = this.shift(i);
BigInteger a0 = this.tail(i);
BigInteger b1 = c.shift(i);
BigInteger b0 = c.tail(i);
BigInteger r2 = a1.multiply(b1);
BigInteger r0 = a0.multiply(b0);