Example: systematic construction of an integer square root program W. Drabent May 2008 Version 3 We want to construct a program computing the integer square root of a non-negative integer n. In other words, the program has to be totally correct w.r.t. precondition P = n ≥ 0 and postcondition Q = (x2 ≤ n < (x + 1)2 ). (The latter also says that x will contain the result). We present the construction with all the details. My intuition tells me that we should try to use a loop. So a skeleton of our program is { P } ?? ; while b do ?? { ⇓ Q }. (I will not write ⇓ below, but remember – we are dealing with total correctness). The most important thing in a loop is its invariant. In the previous example we constructed the invariant by weakening the postcondition: we removed n < (x + 1)2 from Q. It should be easy to obtain the invariant from the precondition and the postcondition from the invariant. Here we introduce a new variable y and take I = ( x2 ≤ n < y 2 ∧ 0 ≤ x < y, ) as the invariant.1 The intuition behind I is that the number we are going to compute is somewhere between x and y. It is good to remember the proof rule for while which we use: {b ∧ I ∧ t=z} S {⇓I ∧ t<z} { I } while b do S { ⇓ ¬b ∧ I } if I ⇒ t ≥ 0 (where t – the bound function – is an integer expression and z is a variable that does not appear in P, b, t or S). 1 The details of constructing the invariant may be explained as follows. First notice that Q ≡ ∃y (x2 ≤ n < y 2 ∧ y = x + 1). Now remove the quantifier; this introduces y as a new free variable. The obtained formula is Q1 = (x2 ≤ n < y 2 ∧ y = x + 1). It is stronger than Q (i.e. Q1 implies Q). The invariant I is obtained by weakening Q1 . If we had only removed y = x + 1 from Q then the resulting formula would have allowed negative values for x, y. To avoid this, we put 0 ≤ x < y. Notice that indeed Q1 implies I, as y = x + 1 implies x < y and Q1 implies 0 ≤ x. (Assume 0 > x; then x + 1 = y ≤ 0 and x2 > y 2 , hence Q1 does not hold.) 1 Q, I and the proof rule for while suggest the choice of b, as it is necessary that ¬b ∧ I implies Q. We achieve this by taking b = (y 6= x+1). P does non imply I, so the loop requires initialization. We are looking for a statement S0 satisfying { P } S0 { I }. It is easy to check that S0 = (x := 0; y := n+1) does the work. (What is wrong with n instead of n + 1?) This is the current version of our program (1) { P }(x := 0; y := n+1); { I } while b do ({ b| ∧ I {z ∧ t = z} } S { I ∧ t < z }) { Q }. P0 Now we are looking for a suitable bound function t. It should be ≥ 0 and it should decrease at each repetition of the loop. Our intention is that the loop shrinks the interval [x..y) containing the solution until the solution is found. So a good candidate for t is the size of this interval: t = y − x. Note that the invariant indeed implies t ≥ 0. It remains to construct the loop body S satisfying the stated pre- and postcondition. S has to decrease t. The future solution is somewhere between x and y. We divide the interval [x..y) into two parts [x..A) and [A..y) (where x < A < y), and choose this part that preserves the invariant (in other words, contains the solution). We do not bother yet about the actual choice of A, and postpone a proof that such A always exists. Replacing [x..y) by [x..A) can be done by y := A, assignment x := A replaces [x..y) by [A..y). As there are two cases, S is likely to be a conditional statement. Together we have S = if b0 then y := A else x | := {z A} | {z } S2 S1 The postcondition for both S1 and S2 is I ∧ t<z. The axiom [ass] gives2 2 {x ≤ n < A2 ∧ 0 ≤{zx < A ∧ A−x < z} } y := A { I ∧ t<z } | (2) { A2 ≤ n < y 2 ∧ 0 ≤ A < y ∧ y−A < z } x := A { I ∧ t<z } (3) R1 | {z } R2 2 Notice that from t < z we obtained A−x < z and y−A < z. The inequalities say that both parts [x..A) and [A..y), into which we divide the interval [x..y), are smaller than [x..y), whose length is z.) 2 We want to apply the rule [if], but this requires the preconditions to be of the form b0 ∧ R and ¬b0 ∧ R. So we are looking for such conditions implying, respectively, R1 and R2 . A good candidate for b0 is n < A2 , as n < A2 occurs in R1 and A2 ≤ n occurs in R2 ). Taking b0 = n < A2 and R = x2 ≤ n < y 2 ∧ 0 ≤ x < A < y ∧ A−x < z ∧ y−A < z we see that indeed b0 ∧ R implies R1 and ¬b0 ∧ R implies R2 . From (2), (3) by rule [cons] we obtain { b0 ∧ R } y := A { I ∧ t<z } (4) { ¬b0 ∧ R } x := A { I ∧ t<z } (5) and then { R } S { I ∧ t<z } We are almost ready, but the required precondition for S is P0 (see (1)). We have to show that P0 implies R; then applying [cons] gives { P0 } S { I ∧ t<z } and completes the correctness proof for the program. Also it remains to decide what is A. Remember that P0 = y 6= x+1 ∧ x2 ≤ n < y 2 ∧ 0 ≤ x < y ∧ y−x = z. Notice first that P0 implies x + 1 < y (we get it from x < y and y 6= x+1). Hence A, which has to satisfy x < A < y, exists. Back to the unproven implication P0 ⇒ R. Compare P0 with R. You see that it remains to show that P0 implies A−x < z ∧ y−A < z. This is easy: from A < y we have A−x < y−x = z; from x < A we have y−A < y−x = z. So we constructed a family of programs (parameterized by the choice of A) and proved their total correctness. Our work can be summarized by the following proof outline. 3 {n ≥ 0} x := 0; y := n+1; { invariant: I } { boundfunction: y − x } while y 6= x + 1 do ( { b ∧ I ∧ y−x = z } | {z b } | {z P0 } {R} if n < A2 then { n<A2 ∧ R } { R1 } y := A else { ¬(n<A2 ) ∧ R } { R2 } x := A { I ∧ y−x < z } ) { x2 ≤ n < (x + 1)2 } Let us choose A. To come to y = x + 1 quickly, it is good to have A close to the middle of the interval [x..y). So A = (x+y)÷2 is a candidate (where ÷ is the integer division, e.g. 7 ÷ 2 = 3). We have to check3 that P0 implies x < A < y. We already know that P0 implies x + 1 < y, hence x + 2 ≤ y. From this we have x + x + 2 ≤ x + y, hence x + 1 ≤ (x + y) ÷ 2 = A; and x + y ≤ y − 2 + y, hence A = (x + y) ÷ 2 ≤ y − 1. Done. 3 Notice that x < (x+y)÷2 < y does not hold for any x < y, e.g. (3 + 4) ÷ 2 = 3 4