CS 228 meeting -*- Outline -*- * specification and correctness (HR 1.6) ** the correctness problem ------------------------------ CORRECTNESS OF PROGRAMS ret_type my_fun(arg_type x) // PRE: precondition // MODIFIES: object-list // POST: postcondition def: a function is *correct* if it has the specified interface, and when called with arguments that satisfy its precondition: a. it terminates b. it has changed no objects that are not listed following MODIFIES c. the postcondition is satisfied ------------------------------ explain what termination isn't (looping) what an object is (C++ variable or ref parameter) what it means for a condition to be satisfied or not *** assertions, state -------------------------------- SATISFACTION OF ASSERTIONS // ASSERT: i > 7 states: i: [ 6 ] i: [ 8 ] i = 7; // ASSERT: i > 6 i = i + 1 // ASSERT: i > 7 ---------------------------- describe the notion of state, and when an assertion is correct or not for a given state note that placement of assertions matters do the last example both forwards and backwards *** post-conditions note that PRE in a function is an assertion about the beginning, and POST is an assertion about the function value and the state at the end. but with post there is a complication -------------------------------- // POST: i > i pre-state: i: [ 22 ] post-state: i: [ 23 ] pre-state: i: [ 22 ] post-state: i: [ 22 ] -------------------------------- discuss -------------------------------- CORRECT OR NOT? extern int z; int foo(int x, int& y) // PRE: x > 0 // MODIFIES: y // POST: y == y * 2 * x // && FCTVAL == y + 7 // (a) int foo(int x, int & y) { y = (y + y) * x; return y + 4 + 2 + 1; } // (b) int foo(int x, int & y) { if (x < 0) { foo(x, y); } else { y = y * 2 * x; } return y+7; } // (c) int foo2(int x, int & y) { y = y * 2 * x; return y+7; } ----------------------- Let them do these ** correctness of while loops (loop invariants) Q: When writing a recursive (or iterative) function in Scheme, what did you think about? should get out the notions: base case, one step, rest of it also making progress Similar concerns for loops, but also have to worry about state, so reasoning takes form of "what's true?" *** deleting a conjunct Ref: Cohen, section 9.1 (also Dijkstra, ...) -------------------------- CORRECTNESS OF WHILE LOOPS DELETING A CONJUNCT Problem: without using % or /, implement void quotient_remainder(int & q, int & r, int x, int y) // PRE: 0 <= x && 0 < y // MODIFIES: q, r // POST: 0 <= r && r < y // && q * y + r = x -------------------------- explain q will be quotient, r remainder guess 2 parts: initialization and loop idea: focus on what relationship among variables will be true this is called the invariant. When finish loop, know the test is false, and invariant still holds. Strategy: one of the conjuncts in the post-condition, negated, will be the loop test. rest will be the invariant for loop test: 0 <= r probably can't use, because hard to establish both the negation of this (0 > r) and (r <= y) q * y + r = x is too complex to use as a test so that leaves the negation of (r < y) as the test, so invariant is: 0 <= r && q * y + r = x SNAPSHOT 1 // INV: 0 <= r && q * y + r = x while (!(r < y)) { } // ASSERT: (r < y) && 0 <= r && q * y + r = x initialization needs to establish the invariant for the loop Strategy, pick something easy to do (the loop does the real work). SNAPSHOT 2 q = 0; r = x; // INV: 0 <= r && q * y + r = x while (!(r < y)) { } // ASSERT: (r < y) && 0 <= r && q * y + r = x now need to worry about making progress. If r < y, need to make it smaller, while maintaining the invariant r = r - y then some math says that to keep the invariant true q = q + 1 SNAPSHOT 3 q = 0; r = x; // INV: 0 <= r && q * y + r = x while (!(r < y)) { r = r - y; q = q + 1; } // ASSERT: (r < y) && 0 <= r && q * y + r = x *** replacing constants with fresh variables ---------------------------- CORRECTNESS OF WHILE LOOPS 2 REPLACING CONSTANTS WITH VARIABLES problem: void arr_min(int b[], int size, int & x) // PRE: size > 0 // MODIFIES: x // POST: x is the minimum element of // b[0..size-1] ---------------------------- Q: What does that post-condition mean more formally? // ASSERT: x is in b[0..size-1] // && for all i from 0 to size-1, x <= b[i] There's no obvious test in either of these conjuncts, as both are difficult to do. So we use a second strategy: replace a constant with a fresh variable (note don't want to change size, although that could serve) Let's have a variable n, and we can thus write // ASSERT: x is in b[0..n-1] // && for all i from 0 to n-1, x <= b[i] && n == size So now we delete this conjunct, so test is !(n == size) This leaves the invariant // INV: x is in b[0..n-1] // && for all i from 0 to n-1, x <= b[i] but this only makes sense if n <= size, because that's where b is defined, so add that to the invariant SNAPSHOT 1 // INV: x is in b[0..n-1] && n <= size // && for all i from 0 to n-1, x <= b[i] while (!(n == size)) { } // ASSERT: n == size && x is in b[0..n-1] && n <= size // && for all i from 0 to n-1, x <= b[i] Q: How to establish the INV? Q: What variables involved? x and n since size > 0, there is at least a 0th element, so start n at 1, and make x be b[0]. SNAPSHOT 2 x = b[0]; int n = 1; // INV: x is in b[0..n-1] && n <= size // && for all i from 0 to n-1, x <= b[i] while (!(n == size)) { } // ASSERT: n == size && x is in b[0..n-1] && n <= size // && for all i from 0 to n-1, x <= b[i] how to make progress? need to increase n, and maintain invariant, which means have to examine b[n+1], compare to x SNAPSHOT 3 x = b[0]; int n = 1; // INV: x is in b[0..n-1] && n <= size // && for all i from 0 to n-1, x <= b[i] while (!(n == size)) { if (b[n] < x) { x = b[n]; } n += 1; } // ASSERT: n == size && x is in b[0..n-1] && n <= size // && for all i from 0 to n-1, x <= b[i] Note this can be coded as a for loop Note that the invariant applies before the test, as always. x = b[0]; int n; // INV: x is in b[0..n-1] && n <= size // && for all i from 0 to n-1, x <= b[i] for (n = 1; !(n == size); n++) { if (b[n] < x) { x = b[n]; } } // ASSERT: n == size && x is in b[0..n-1] && n <= size // && for all i from 0 to n-1, x <= b[i] *** summary This idea of developing the program and it's proof ensures an understandable and correct program (See the Cohen book). The idea of proving the program meets the spec is called verification. it's a complement to testing. ------------------------------- SUMMARY OF LOOP IDEAS Try to find an invariant by 1. deleting a conjunct from the desired postcondition 2. If that doesn't work, try replacing constants with variables Questions: What changes? (loop test) What relationship maintained? (invariant) How to establish the invariant? How to make progress while maintaining the invariant? ------------------------------- What an invariant means (and relationship to H&R's book) ------------------------------- WHAT INV ASSERTIONS MEAN // INV: 0 <= r && q * y + r = x while (!(r < y)) { r = r - y; q = q + 1; } means // ASSERT: 0 <= r && q * y + r = x while (!(r < y)) { // ASSERT: 0 <= r && q * y + r = x r = r - y; q = q + 1; // ASSERT: 0 <= r && q * y + r = x } Headington and Riley's notation: while (!(r < y)) { // INV (before test): // 0 <= r && q * y + r = x r = r - y; q = q + 1; } ------------------------------- discuss side-effects in loops