Recitation 2

Reviewing invariants

Multiplication algorithm

Accessing the course bulletin board

Exercise

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.

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 * 2^{5} + x * 2^{2} + x * 2^{0}

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 2^{0}) 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 2^{1}
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.

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.

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