Generic Programming

Part II: Algorithms on Algebraic Structures

Chapter 4: Arithmetic

 Last updated 17 June 1998

## § 4.1 -- Power: The Russian Peasant Algorithm

An operation of great utility, one that is supported directly by many languages, is that of raising a number to a positive integer power. In this section we discuss this algorithm generically.

For any set with a binary operation *, we can define a power function inductively by

 power(x, 1) = x power(x, n) = power(x, n-1) * x if n > 1

For a general binary operation, however, this power function is not very interesting: if, for example, we defined it with the operation applied in the other order, we would in general get an entirely different operation. The power function becomes interesting when we require the operation to be associative. If * is associative, we can show easily by induction the familiar law of exponents:

 power(x, m+n) = power(x, m) * power(x, n)

As we shall see, this property not only makes the power function interesting; it also makes it possible to compute it efficiently.

For a generic power algorithm, it is clear that their are two types involved: the type of n should be some type that can represent natural numbers, and the type of x should be of some type supporting an associative binary operation. Mathematicians call such a set a semigroup. This suggests the interface

 ``` template SemigroupElement power(SemigroupElement a, Integer n, SemigroupOperation op) { .............. } ```

(Here as always the template parameter names are merely mnemonics, intended to suggest requirements on the template arguments.)

It is, however, a little more convenient to add an additional requirement on the semigroup, namely that it possess an identity, that is an element e such that

 op(x, e) = op(e, x) = x for every x.

Such a semigroup is known as a monoid, and we are not really losing significant generality by restricting ourselves to monoids, since it is a mathematical fact that we can always adjoin an identity element to any semigroup.

This gives the interface

 ``` template MonoidElement power(MonoidElement a, Integer n, MonoidOperation op) { .............. } ```

Of course only the names have been changed!

We now proceed to try to develop an algorithm.

Let's start by initializing the result:

 ``` template MonoidElement power(MonoidElement a, Integer n, MonoidOperation op) { MonoidElement result = identity_element(op); ................ return result; } ```

This initialization accomplishes something very important: if a0 is the initial value of a and n0 is the initial value of n, we have established the equality op(result, power(a, n)) = power(a0, n0). If we can reduce n to zero keeping this relation invariant, we will be sure of having a correct algorithm.

This suggests the following straightforward solution:

 ``` template MonoidElement power(MonoidElement a, Integer n, MonoidOperation op) { MonoidElement result = identity_element(op); while (n != 0) { result = op(result, a); --n; } return result; } ```

This is correct, but not very efficient: it has linear complexity in n and would be quite unsatisfactory for very large values of n. Note that this algorithm uses only the inductive definition of the power function. It does not exploit the law of exponents, and could be used perfectly well for arbitrary binary operations. Applying the law of exponents, however, allows a major improvement. The identity

 power(x, m+n) = op(power(x, m), power(x, n))
when m is equal to n gives
 power(x, 2n) = square(power(x, n), op)
where we define square(x, op) to be op(x, x).

This means that if n is even, we can maintain the invariant with a much greater reduction in n: we can square a and halve n. This observation leads to the improved algorithm

 ``` template MonoidElement power(MonoidElement a, Integer n, MonoidOperation op) { MonoidElement result = identity_element(op); while (n != 0) { if (is_even(n)) { square(a, op); halve(n); } else { result = op(result, a); --n; } } return result; } ```

This version has logarithmic complexity. Probably we're not going to achieve dramatic improvement from this point, but let's see if we can squeeze it a bit.

Note that in the else branch, once we have reduced n by 1 we know it is even, so we have a redundant test in the next iteration. We might as well fold it in now. This gives us

 ``` template MonoidElement power(MonoidElement a, Integer n, MonoidOperation op) { MonoidElement result = identity_element(op); while (n != 0) { if (is_even(n)) { square(a, op); halve(n); } else { result = op(result, a); --n; if (n != 0) { square(a, op); halve(n); } } } return result; } ```

Now if n is odd, the combination

 ``` --n; halve(n); ```
is equivalent to just
 ``` halve(n); ```

so we can simplify this to

 ``` template MonoidElement power(MonoidElement a, Integer n, MonoidOperation op) { MonoidElement result = identity_element(op); while (n != 0) { if (is_even(n)) { square(a, op); halve(n); } else { result = op(result, a); if (n != 1) { square(a, op); halve(n); } } } return result; } ```

And the comparison against 1 can be eliminated by separating out the last iteration, provided we are a little careful: if n is not zero initiallly, then repeated halvings must take it through 1. So let's treat the case n == 0 separately at the beginning:

 ``` template MonoidElement power(MonoidElement a, Integer n, MonoidOperation op) { if (n == 0) return identity_element(op); MonoidElement result = identity_element(op); while (n != 1) { if (is_even(n)) { square(a, op); halve(n); } else { result = op(result, a); square(a, op); halve(n); } } result = op(result, a); return result; } ```

But we can do better if we note that whenver n is non-zero and even, it is wasteful to check for both n non-zero and n even, since repeating halving must eventually make n odd. Applying this observation gives us

 ``` template MonoidElement power(MonoidElement a, Integer n, MonoidOperation op) { if (n == 0) return identity_element(op); MonoidElement result = identity_element(op); while (n != 1) { if (is_even(n)) { square(a, op); halve(n); } else { result = op(result, a); do { square(a, op); halve(n); } while (is_even(n)); } } result = op(result, a); return result; } ```
But now the inner loop always terminates with n odd. If we could get n odd at the beginning of the first iteration, we could eliminate the is_even test altogether. But we can use our previous observation again to guarantee this:
 ``` template MonoidElement power(MonoidElement a, Integer n, MonoidOperation op) { if (n == 0) return identity_element(op); MonoidElement result = identity_element(op); while (is_even(n)) { halve(n); square(a, op); } while (n != 1) { result = op(result, a); do { square(a, op); halve(n); } while (is_even(n)); } result = op(result, a); return result; } ```
Now either the first iteration of the outer loop or the instruction following the loop has a multiplication by the identity. This can be eliminated if we assign a to result just before the loop and move the multiplication to the end of the loop:
 ``` template MonoidElement power(MonoidElement a, Integer n, MonoidOperation op) { if (n == 0) return identity_element(op); while (is_even(n)) { halve(n); square(a, op); } MonoidElement result = a; while (n != 1) { do { square(a, op); halve(n); } while (is_even(n)); result = op(result, a); } return result; } ```

## § 4.3 -- Greatest Common Divisor

The most important fact about the greatest common divisor d of two integers x and y is that there always exist integers u and v for which

 ``` (1) ux + vy = d ```
In some applications, finding these integers is more important than finding the gcd. In particular, if gcd(x, y) = 1, then finding u and v satisfying (1) solves the problem of finding a multiplicative inverse for x modulo y or for y modulo x.

Fortunately, solving this problem is not much more difficult that finding the gcd, and we can extend the Euclidean algorithm to provide a solution at very little additional cost. We call this the Extended Euclidean Algorithm.

Given x and y, we want to find u and v such that

 ``` (1) ux + vy = d ```
where d is the gcd of x and y. An obvious approach is to replace d by a variable r, find some initial value of r for which
 ``` (1) ux + vy = r ```
is easy to solve, and then find a way to decrease r while maintaining the equation (2).

Now there are two initial values for r for which (2) is easy to solve, namely x and y, for which we have the solutions

 ``` u = 1, v = 0 ```
and
 ``` u = 0, v = 1 ```
respectivley.

So let's introduce variables r0, u0, v0, r1, u1, v1, and initialize them as follows:

 ``` r0 = x; u0 = 1; v0 = 0; r1 = y; u1 = 0; v1 = 1; ```

so that we have the equations

 ```(3) u0 * x + v0 * y == r0 ```
and
 ```(3) u1 * x + v1 * y == r1 ```

Now we can get a simliar equation with a smaller r by setting

 ``` q = r0/r1; r = r0 - q * r1; u = u0 = q * u1; v = v0 - q * v1; ```
and we have
 ``` ux + vy = r, where r < r1. ```
as we see simply by multiplying equation (4) by q and subtracting from equation (3).

This leads to the following code:

 ``` template triple extended_gcd(const EuclideanRingElement & x, const EuclideanRingElement & y) { if (x == 0) return make_triple(0, 1, y); if (y == 0) return make_triple(1, 0, x); EuclideanRingElement r0 = x, u0 = 1, r1 = y, u1 = 0, v1 = 1; while (r1 != 0) { EuclideanRingElement q = r0 / r1; EuclideanRingElement r = r0 - q * r1; EuclideanRingElement u = u0 - q * u1; EuclideanRingElement v = v0 - q * v1; r0 = r1; u0 = u1; v0 = v1; r1 = r; u1 = u; v0 = v; } return make_triple(u0, v0, r0); } ```

Now let's see if we can optimize this a little. We notice that the variables v0, v1, v are not used in the computation of the other variables. They are just going along for the ride, so to speak, except that v0 is part of the final result. Their usefulness is that they let us see we are maintaining the invariants throughout the computation.

But we can simply compute v0 after we exit the loop, since it is just the solution of

 ```u0 * x + v0 * y == r0 ```
Eliminating the v's from the code gives us
 ``` template triple extended_gcd(const EuclideanRingElement & x, const EuclideanRingElement & y) { if (x == 0) return make_triple(0, 1, y); if (y == 0) return make_triple(1, 0, x); EuclideanRingElement r0 = x, r1 = y, u1 = 0; while (r1 != 0) { EuclideanRingElement q = r0 / r1; EuclideanRingElement r = r0 - q * r1; EuclideanRingElement u = u0 - q * u1; r0 = r1; u0 = u1; r1 = r; u1 = u; } return make_triple(u0, (r0 - y0 * x) / y, r0); } ```