The Fibonacci function is often defined as follows:
fib.0 = 0 |
fib.1 = 1 |
fib.k = fib.(k−2) + fib.(k−1) (for k>1) |
Viewing this function as defining the sequence
gives rise to the so-called Fibonacci sequence
in which, beginning at the third element, each element is the sum of the preceding two.
Let's develop a program that, given as input a positive integer N, calculates fib.N. Here is a specification:
|
It seems likely that we will need to use repetition to solve this problem. Applying the replace a constant by a variable heuristic, we can rewrite the postcondition Q as the slightly stronger
Applying the remove a conjunct heuristic, we take the first conjunct of Q' as a loop invariant and the second as the negation of the loop's guard. We obtain the following refinement of the specification:
|
First we consider how to initialize n
and m
so as
to "truthify" the proposed loop invariant. That is, we wish to find
expressions to replace the question marks so as to truthify the Hoare Triple
{N>0} n,m := ?,? {I}
(The post-condition of initialization is the loop invariant;
the pre-condition of this program is N > 0, which, because it involves
only constants, we view as a "global invariant".)
The obvious possibilities for initialization
are n,m := 0,0
(reflecting the fact that fib.0 = 0)
and n,m := 1,1
(reflecting the fact that fib.1 = 1).
We choose the latter for reasons that will become clear later.
Either way, this suggests that n
should be increased within
the body of the loop so as to eventually reach value N
.
The simplest way to do that is by incrementing n
, so that's
what we propose. These refinements lead to the program
|[ con N : int; { N > 0 } var m : int; var n : int; n,m := 1,1; { I : m = fib.n } do n ≠ N ---> n,m := n+1,?; od { Q' : m = fib.n ∧ n = N } { Q : m = fib.N } ]| |
It remains to determine how to update m
inside the body of
the loop. We attempt to calculate the appropriate expression E
by proving item (ii) among the five proof obligations for a loop:
(ii) {I ∧ B} n,m := n+1,E {I} Assume I and B. wp.(n,m := n+1,E).I = < wp assignment law > I(n,m := n+1,E) = < defn of I; textual sub. > E = fib.(n+1) |
At this point, our only manipulative opportunity arises from the third
equation in the definition of fib. However, for that equation
to apply requires that n+1>1
, i.e., n>0
.
As we initialized n
to 1 and thereafter all changes to
n
are due to incrementing it, it is clear that
n>0
will be true.
(Note: This explains why we initialized n
to 1 rather
than to 0! End of note.)
Hence, we incorporate this condition
into the loop invariant so that we can use it here as an assumption.
Starting the proof over again with the stronger version of I
(namely, I = I0 ∧ I1
, where
I0 : m = fib.n
and I1 : n > 0
),
we get
(ii) {I ∧ B} n,m := n+1,E {I}
Assume I (i.e., m = fib.n and n > 0) and B (i.e., n ≠ N). wp.(n,m := n+1,E).I = < wp assignment law > I(n,m := n+1,E) = < defn of I; textual sub. > E = fib.(n+1) ∧ n+1 > 0 = < assumption n > 0 implies 2nd conjunct; (Gries 3.39) > E = fib.(n+1) = < assumption n > 0 implies n+1 > 1, justifying use of 3rd equation in defn of fib > E = fib.(n-1) + fib.n = < assumption m = fib.n > E = fib.(n-1) + m |
At this point, we observe that it would be nice to have a program variable, say r, guaranteed to have value fib.(n−1) at this point in execution, because then we could finish the derivation of E:
|
As we are free to introduce such a variable, we do so! That is, we
strengthen the loop invariant by adding
I2 : r = fib.(n-1)
as a new conjunct.
Of course, doing this brings with it the responsibility to introduce
code to initialize r
so as to truthify I2
and to update r
during each loop iteration so as to preserve
the truth of I2
. The refined program is as follows:
|[ con N : int; { N > 0 } var m, r : int; var n : int; n, m, r := 1, 1, ?; { I : I0 ∧ I1 ∧ I2, where I0 : m = fib.n, I1 : n > 0, and I2 : r = fib.(n-1) } do n ≠ N ---> n, m, r := n+1, r+m, ?; od { Q' : m = fib.n ∧ n = N } { Q : m = fib.N } ]| |
Let's calculate the proper initialization to r, by deriving an
expression F
satisfying {N>0} n,m,r := 1,1,F {I}
.
By the Hoare Triple Assignment Law),
this is equivalent to [N>0 ===> I(n,m,r := 1,1,F)]
.
As assuming the antecedant provides no value, we simply prove the
consequent:
I(n,m,r := 1,1,F) = < defn of I; text. sub. > 1 = fib.1 ∧ 1 > 0 ∧ F = fib.(1-1) = < defn of fib; arithmetic, (Gries 3.39) > F = fib.0 = < defn of fib > F = 0 |
Next we determine how to modify r
during each iteration.
As our previous work has demonstrated that execution of the loop body
preserves the truth of I0
and
I1
(neither of which mentions r
),
we use as a postcondition only I2
.
|
|
All that remains is to provide a bound function. Here, the obvious
choice is t : N-n
. Technically, in order to prove item
(iv), we need to include the condition n≤N
within the loop invariant. (We can incorporate this into
I1
.) Clearly, this condition is truthified
by initializing n
to 1 (given that N>0
is
a global invariant) and preserved by incrementing n
when
it is known that n<N
, as is guaranteed by
I ∧ B
.
Here is the final, annotated program:
|
Given the roles played by variables m and r, one could make a reasonable argument that fibThis and fibPrev, respectively, would be better names.
Suppose that the precondition had been the weaker N≥0
rather than N>0
. Since the program we developed above
works for any N
strictly greater than zero, all we need to
do is to introduce a selection command, one branch of which handles the
case N=0
and the other of which handles the case
N>0
. Of course, the body of the latter branch would be
the loop (preceded by initialization code) that we developed above.
The body of the former branch would simply set m
to zero,
as fib.0 = 0. Here is that program:
|
But this solution lacks some of the elegance of the original.
Can we somehow avoid having to introduce the selection command in order
to handle the case N = 0
?
It turns out that we can!
The only place where our reasoning relies upon n>0 is in the
proof of loop checklist item (ii), as there we made use of the
fact that, by definition, n>0 implies
fib.(n+1) = fib.(n−1) + fib.n.
(But this equation fails to hold for n = 0, because
fib.(−1) is not even defined.)
But there is no reason why we cannot modify the definition of fib to enlarge its domain, as long as we change nothing else about the function. So what we seek is a value for fib.(-1) that is consistent with fib.1 = fib.(-1) + fib.0 (the recursive case of the function instantiated with k=1). But this works out to
All we have to change to make our original solution work is to make the loop initialization code be
n,m,r := 0,0,1;
The loop invariant is weakened insofar as I2
becomes
I2 : 0≤n≤N
Another generalization that could be made is to define fib.0 = A and fib.1 = B, where A and B are "input constants". (Thus our domain-enlarged function has fib.(-1) = B−A.) Then all we have to change in the program is to make the initialization assignment be
n,m,r := 0,A,B-A;