An example using invariants: Multiplication Algorithm
Consider the problem of multiplying two integers x and y and returning the result.
The way we can approach this is by thinking about expressing one of the integers as the sum of a sequence of powers of 2. For example,
x * 37 = x * (32 + 4 + 1) = x * 32 + x * 4 + x * 1
= x * 25 + x * 22 + x * 20
If we think about 37 in binary (shown with 8 bits here for simplicity):
00100101
we see that there is a 1 for each power of 2 we need to use in our
computation. Starting with an overall result of 0,
we see that the right most bit (representing 20) is 1,
so we need to add x to our overall result.
If we shift 37 to the right one position, the next bit to consider
is again in the rightmost position but it now represents 21
so we should shift x left one position to take this into account so if
the rightmost bit is 1 again, we can add the correct x value to the
result (that is, 2 * the original x). We repeat this process until
we've processed all the bits in 37.
Here is a function that performs this computation:
int mult(int x, int y)
//@requires x >= 0 && y >= 0;
//@ensures \result == x*y;
{
int n = x; int k = y;
int res = 0;
while (n != 0)
//@loop_invariant __________________
{ if ((k & 1) == 1) res = res + n;
k = k >> 1;
n = n << 1;
}
return res;
}
One thing you should notice is that we don't alter the values of x and y.
Instead, we create local variables with copies of these values so we can
alter them without changing the initial parameters. This is useful so we
can use the initial parameters in our annotations and know that they
represent the original values passed to the function.
Let's trace this with the example above to see how it works, with x = 2
and y = 37. Recall that k & 1 determines the value of the last bit
of k to determine if we add in y or not.
Let's assuming we're dealing with 8 bits values for simplicity (the results
generalize to 32 bits easily):
n k res
2 37 0
4 18 2
8 9 2
16 4 10
32 2 10
64 1 10
-128 0 74
0 0 74
Based on this trace, we conjecture that the loop invariant is:
x*y == k*n + res
Looking at the loop invariant, it says that our current result plus k * n is
always equal to x * y. That is, if we stopped part way through the loop
with some partial result, the remaining k and n multiplied together would
complete the total product.
Let's show that the invariant holds for this loop.
1. Before the loop condition is tested for the first time,
k = x and n = y and res = 0:
x*y = k*n + res
x*y = x*y + 0
x*y = x*y
2. Assume x*y = k*n+res at the start of an iteration.
We wish to show that x*y = k'*n' + res' at the end of that iteration
(just before the loop condition is tested again).
We consider two cases.
a. k is even, so k & 1 = 0.
Then k' = k>>1 = k/2 and n' = n*2 and res' = res.
Hence k'*n' + res'
= (k/2)*n*2 + res
= k*n+res
= x*y.
b. k is odd, so k & 1 = 1.
Then k' = k>>1 = (k-1)/2 and n' = n*2 and res' = res + n.
Hence k'*n' + res'
= (k-1)/2*n*2 + res + n
= (k-1)*n + res + n
= k*n - n + res + n
= k*n+res
= x*y
(So the loop invariant holds at the end of this iteration,
which means it also holds for the start of the next iteration
since the loop condition does not change the values of any variables.)
Our reasoning show that the invariant holds for EVERY iteration, since we
know it holds at the start of iteration #1. Step 2 above shows
that if it holds for iteration #1, it must at the start of
iteration #2. If it holds at the start of iteration #2, by step 2
again, it must hold at the start of iteration #3, etc.
When reasoning with loop invariants, we
also need to show that the loop must terminate. That is, we could get into
an infinite loop where the loop invariant is always true, but we'd never
return a result from our computation. For this example,
after at most 32
left shifts on n, n must be 0 causing the loop to terminate. This is because
each left shift inserts a 0 into the right end of the integer and drops off
the leftmost bit. After at most 32 shifts, the entire integer must be all 0's.
We're shown that the loop invariant is valid for the loop and that the loop
will terminate.
Now, what do we know immediately after
the loop terminates? We know the loop invariant is true (since the loop condition
doesn't change any values in our function) and n == 0. Thus:
(x*y == k*n + res) && n == 0 ===> x*y = res
Since the only thing we do after the loop terminates is return the result,
we have shown that we will always return x*y as indicated by the postcondition.
So one of the reasons to have a strong loop invariant is to be able to show
that the result of the computation after the loop terminates is the
postcondition you're expecting from your function.
Exercise: Does this function work if x or y is negative?
written by Tom Cortina, 1/21/11