/* PROOF OF CORRECTNESS: N_SQUARED(N): compute N^2 This example will show how to prove programming code to be mathematically correct. This is used instead of testing, which can never prove the absence of bugs. However, proof of correctness does not scale very easily to large software. This is going to be the first example and will be quite detailed. Although there is math, you should be able to handle these formulas. Remember in CS 2150 Discrete Structures you learned Proof by Induction? You used it to prove a formula is correct. There is a base case (say n=1) where the formula works. Then the induction step: assume true for arbitrary k, and prove true for k+1. Therefore, it is always true. Let's try one: 1 + 3 + 5 + ... + (2n-1) = n^2 = n*n Base case: n=1 1 + ... + 2*1-1 = 1 = 1^2 Induction step: Assume true for k: 1 + ... + (2k-1) = k^2 Add 2k+1 to both sides: 1 + ... + (2k-1) + 2k+1 = k^2 + 2k+1 1 + ... + (2k-1) + 2(k+1)-1 = k^2 + 2k+1 1 + ... + (2k-1) + 2(k+1)-1 = (k+1)^2 // multiply (k+1)(k+1) and you get k^2 + 2k + 1 We're done. It's the same formula but now true for k+1. Why did I add 2k+1? I wrote the formula that I desired to get, expanded it, and saw that it had increased by 2k+1. Now for proof of correctness for programming code. The above formula has been programmed with the following C code. It is slightly different in the presentation because it utilizes the fact that the formula really adds up n odd numbers. For example, say n=4: 1 + 3 + 5 + 7 = 16 = 4^2 */ #include int n_squared(int n) { int i, k, z; // PRE-CONDITION: n > 0 // This states any necessary pre-conditions that must hold for the // parameters. The code works for n=0 too, but the proof above is strange // in that instance. z = 0; // accumulate the sum k = 1; // increment to odd numbers i = 0; // loop index: 0..n-1 while (i < n) { z = z + k; // accumulate i = i + 1; // increment k = k + 2; // skip to next odd } return z; // z = x^2 } int main() { printf("n_squared(4)=%d\n",n_squared(4)); } /* Compile and run the code to see that it works for n=4: gcc -o n_squared n_squared.c Here is an argument (not a proof) that the code computes the formula above: 1. Start k at 1, keep incrementing k by 2, to keep it odd. 2. Start z at 0, keep adding k to it, so z accumulates these odd values. 3. Start i at 0, keep incrementing until just before it hits n. 4. z will be the summation of the correct n odd numbers. Now let's desk-check or trace the code to see how it behaves in detail by iteration, assuming n = 4: i k z = i^2 ----------------- 0 1 0 = 0^2 1 3 1 = 1^2 2 5 4 = 2^2 3 7 9 = 3^2 4 9 16 = 4^2 Why did I pick to list i^2 with each iteration? If I list i, k, z, it becomes apparent that z is always i^2. This iteration stops when i=4=n and we see that z=i^2=n^2. And that's exactly the purpose of the function (called the POST-CONDITION). Now, let's add a LOOP INVARIANT to the code. The INVARIANT is a statement of truth, about each iteration of code. We think from the table above, that z will ALWAYS be i^2 at the beginning and ending of each iteration. Note that it has been listed four times, and that's because each location is really the same point in the code; there are no modifying steps in-between. int n_squared(int n) { int i, k, z; // PRE-CONDITION: n > 0 z = 0; k = 1; i = 0; // INVARIANT: z = i^2 while (i < n) { // INVARIANT: z = i^2 z = z + k; i = i + 1; k = k + 2; // INVARIANT: z = i^2 } // INVARIANT: z = i^2 // POST-CONDITION: z = n^2 return z; } The table shows that i=4=n at the end of execution. This is important because then z=i^2=n^2, the POST-CONDITION. But we're going to need to prove this (and more). Here's what I call "using your eyeballs". Look at the while loop: i=n. This is called the EXIT CONDITION. But that doesn't prove that it is exactly n. I'm going to add another condition to my LOOP INVARIANT: i>=n. Why? How can i be simultaneously >=n and <=n? Only by being exactly n. The proof is still not complete but here's a filled-in version: int n_squared(int n) { int i, k, z; // PRE-CONDITION: n > 0 z = 0; k = 1; i = 0; // INVARIANT: z = i^2 and i <= n while (i < n) { // INVARIANT: z = i^2 and i <= n z = z + k; i = i + 1; k = k + 2; // INVARIANT: z = i^2 and i <= n } // INVARIANT: z = i^2 and i <= n and EXIT: i >= n // exited the loop // z = i^2 and i=n // this follows from the above // POST-CONDITION: z = n^2 return z; } Note: the INVARIANT and the EXIT prove the POST-CONDITION. If that's not the case, then the INVARIANT is either wrong (and don't bother to try to prove it) or irrelevant (yes, it is true that the color green equals blue plus yellow but that doesn't help prove that z=n^2). WARNING: do NOT put what your eyeballs tell you into the INVARIANT. Use the EXIT CONDITION and a restriction on the loop index to force the variable to be what you intuitively know. Now let's do the base case: prove the INVARIANT is true before the 1st iteration: z = 0; // z = 0 // must be true because of the assignment statement k = 1; // z = 0 and k = 1 i = 0; // z = 0 and k = 1 and i = 0 // INVARIANT: z = i^2 and i <= n // this is true given the above That's the easy part. But if it doesn't work, then the proof doesn't work. Now for the step. We'll assume the INVARIANT is true for some arbitrary iteration, and show that it is still true for the next iteration. Our first attempt is NOT going to be successful. We'll have to add something later. // INVARIANT: z = i^2 and i <= n // make this assumption while (i < n) { // z = i^2 and i < n // yes, the invariant is still true but use the TIGHTER // condition of the while loop. See why in a few steps. z = z + k; // z = i^2 + k and i < n // z use to be i^2, but we just added k to it, // so it must be larger by k now. i = i + 1; // z = (i-1)^2 + k and i <= n // z used to be i^2, plus k, but now i has been // incremented. This is the proper relationship. // And i less than OR equal to n because it just // got bigger. Now it's like the invariant and // that's why we used the tighter condition of // the while loop. k = k + 2; // z = (i-1)^2 + k-2 and i<=n // because k just incremented by 2, we need to // rebalance for z. // we can't prove the next line, so we haven't completed the step. The problem // is that k is part of the formula and we can't simplify. See the table below. // INVARIANT: z = i^2 and i <= n } // INVARIANT: z = i^2 and i <= n and EXIT: i >= n // z = i^2 and i=n // POST-CONDITION: z = n^2 return z; } Here's the same table but with a new column for 2i+1: i k z = i^2 2i+1 ------------------------ 0 1 0 = 0^2 1 1 3 1 = 1^2 3 2 5 4 = 2^2 5 3 7 9 = 3^2 7 4 9 16 = 4^2 9 What do you see? k always seems to be 2i+1. This needs to be added to our invariant and we need to prove it. Before that, let's see how it completes the stumbling block above: // z = (i-1)^2 + k - 2 and i<=n Plug in 2i+1 for k: // z = (i-1)^2 + (2i+1) - 2 // z = i^2 -2i + 1 + 2i + 1 - 2 // z = i^2 // everything else just cancels out Note: we are back to the essential part of our invariant. Now to compete the entire proof. First: INVARIANT: z = i^2 and i <= n and k = 2i+1 The proof below is formal and with only one line of narrative (more would just get in the way). The new aspect is just the proof that k = 2i+1, which is true initially (base case). int n_squared(int n) { int i, k, z; // PRE-CONDITION: n > 0 z = 0; // z = 0 k = 1; // z = 0 and k = 1 i = 0; // z = 0 and k = 1 and i = 0 // INVARIANT: z = i^2 and i <= n and k = 2i+1 while (i < n) { // z = i^2 and i < n and k = 2i+1 z = z + k; // z = i^2 + k and i < n and k = 2i+1 i = i + 1; // z = (i-1)^2 + k and i <= n and k = 2(i-1)+1 k = k + 2; // z = (i-1)^2 + k-2 and i <= n and k = 2(i-1)+1 + 2 // THIS IS THE BIG ONE // z = (i-1)^2 + k-2 and i <= n and k = 2i+1 // z = (i-1)^2 + (2i+1)-2 and i <= n and k = 2i+1 // z = i^2 -2i + 1 + 2i + 1 - 2 and i <= n and k = 2i+1 // INVARIANT: z = i^2 and i <= n and k = 2i+1 } // INVARIANT: z = i^2 and i <= n and k = 2i+1 and EXIT: i >= n // z = i^2 and i=n // POST-CONDITION: z = n^2 return z; } Although the math is very detailed, it is really just adding and subtracting. In fact, a program - similar to a compiler - could do this for us but it could not determine the INVARIANT. That requires an intelligent and creative test engineer (you!). How to come up with the INVARIANT? Here's the one from above: z = i^2 and i <= n and k = 2i+1 The variable z represents the partial computation. That's what loops do: they compute results at each iteration and eventually yield the final computation. At any iteration i, z = i^2. The restriction on the loop index, i <= n, combined with the EXIT CONDITION, (i>=n) yields i = n which our "eyeballs" intuitively told us. This means that z = i^2 = n^2, the POST-CONDITION and the purpose of the function. These are two common features we'll see in later problems. The final term, k = 2i + 1, arose because the proof hit a stumbling block. This extra term usually will not happen. Here are the techniques that can help you: 1. Read and analyze code: try to figure out the logic and the relationships between data. For instance, z = i^2 would have really helped. 2. Desk-check with tables: if the above doesn't work, play with the code to see how it behaves. 3. Work backwards: z = n! is the POST-CONDITION. If z = i! and i = n, that works. The EXIT CONDITION is automatic: i >= n. If the INVARIANT has i <= n, then that forces i = n. 4. Draw pictures: try to determine the partial computation. For example: 16 +-----+ | | | | | | | 7 | | | | | | | 9 +-----+ | | | | | 5 | | | | | 4 +-----+ | | | 3 | | | 1 +-----+ | 1 | z = 0 +-----+ 5. Trial-and-error: if all else fails you may have to resort to guessing formulas. But this could take forever. However, if you get close, you might only be off by 1. And then you can refine. 6. In some proofs, can work backwards from POST. See factorial5.html But in all circumstances: 1. Base case must be true. 2. Step case must work. 3. INVARIANT and EXIT must yield POST-CONDITION. Here is the same but more detail: // PRE-CONDITION initialization; // LOOP INVARIANT while (cond) { statements; // LOOP INVARIANT } // LOOP INVARIANT and EXIT CONDITION (i.e. NOT cond) // POST CONDITION Must prove the following: 1. PRE-CONDITION and initialization => LOOP INVARIANT 2. LOOP INVARIANT and cond and statements => LOOP INVARIANT (i.e. going around the loop preserves the invariant 3. LOOP INVARIANT and EXIT CONDITION => POST CONDITION One more rule helps to handle sequential statements: Sequence Rule: CONDITION1 CONDITION2 CONDITION1 S1; and S2; => S1; S2; CONDITION2 CONDITION3 CONITION3 This makes logical sense. Now, back to the README and the next problems. */