15-122 Principles of Imperative Computation
Recitation 2

Reviewing invariants
Multiplication algorithm
Accessing the course bulletin board
Exercise

Reviewing Invariants

In class, we discussed how to implement linear search as follows:

int find(int x, int[] A, int n)
//@requires 0 <= n && n <= \length(A);
//@requires is_sorted(A,n);
{ int i;
  for (i = 0; i < n && A[i] <= x; i++)
    //@loop_invariant 0 <= i && i <= n;
    //@loop_invariant i == 0 || A[i-1] < x;
    if (A[i] == x) return i;
  return -1;
}

Let's look at the second loop invariant more carefully. First, it uses a programming technique called short-circuit evaluation (or sometimes called lazy evaluation) where the second condition is evaluated at runtime only if necessary. In this example, if i is equal to 0, then the invariant is true so there is no need to test the second condition. In fact, this is desired since A[i-1] will yield an array index out of bounds exception when i equals 0. In general, if we have the condition A || B, if A is true, B is not evaluated. If we have the condition A && B, if A is false, B is not evaluated. So be careful, in programming languages that provide short-circuit evaluation, A || B does not always yield the same result as B || A. The order does matter sometimes, and we will take advantage of this sometimes.

Now let's look at the invariant:

i == 0 || A[i-1] < x

If this condition is true at the start of a loop iteration, is it true at the end of an iteration?

When we test the loop condition the first time, the invariant holds trivially. After the each iteration is complete, i' = i + 1, so we wish to show that

A[i'-1] < x ==> A[i+1-1] < x ==> A[i] < x   
(Note: i==0 is only relevant for the start of the first iteration, otherwise i==0 is false and we need to test the 2nd condition.)

The loop iteration starts with A[i] <= x and to get to the end of the iteration (without returning from the function), A[i] != x. Thus, A[i] < x.

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

Here is a function that performs this computation:

int mult(int x, int y)
//@ensures \result == x*y;
{
  int k = x; int n = y;
  int res = 0;
  while (n != 0)
    //@loop_invariant x * y == k * n + res;
    { 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

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. When the loop is entered, k = x and n = y and res = 0
      so x*y = k*n = k*n + res
   2. Assume x*y = k*n+res. We wish to show that x*y = k'*n' + res'
      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

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, clearly after at most 32 left shifts on n, n must be 0 causing the loop to 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.

Accessing the bulletin board

Remember that there is a bulletin board for this course so you can ask general questions to the course staff and others in the class. Please remember to use the bulletin board appropriately and do not post homework answers or inappropriate messages.

To access the bulletin board, access your andrew mail using SquirrelMail by going to webmail.andrew.cmu.edu. Once you're there, click the Folders link and then go to the Subscribe section. Highlight academic.cs.15-122 and click Subscribe. You should then see the bboard listed in your folders on the left side of the webpage. You can read messages and post messages just like you would for email.

Exercise

Here is a simple tester to test the multiplication function discussed above:

int main () {
  int i; int j;
  for (i = 0; i < (1<<10); i++) {
    for (j = 0; j < (1<<10); j++) {
      assert (mult(i,j) == i*j, "pos * pos failed");
      assert (mult(-i,j) == -i*j, "neg * pos failed");
      assert (mult(i,-j) == i*-j, "pos * neg failed");
      assert (mult(-i,-j) == i*j, "neg * neg failed");
    }
  }
  return 0;
}

Enter this in and test it out. Then modify it to include even more tests (for larger values of i and j) and use the time command to see how long the tester runs. Do the results of the timing experiments make sense to you?

To use the time function, simply type:

time ./a.out

or replace a.out with the name of the executable file you want to time. The first value in the time output is the number of seconds used to run your program not including operating system overhead (i.e. time your program was delayed while the operating system did other things).


written by Tom Cortina, 9/07/10