Solution5ProgramSpecs

advertisement
M225: SOFTWARE ENGINEERING
Unassessed Coursework 5: Specifying Java Programs and While Loops
The aims of this tutorial are to enable you to practice with:
» mapping Object-Z class schemas into Java programs and programs specifications;
» reasoning about the correctness of small Java methods, using reasoning rules for assignment,
if-then-else statements and method calls.
» reasoning about the correctness of loop structures using loop invariants and loop variants.
» apply the methodology described in Unit 9 in order to define loop specifications and derive
code that satisfies these specifications
1.
Consider the CreditCard class schema and its corresponding Java program and program
specifications given in slide 8 of the lecture notes Unit 7.
a. Implement the method “withdrawAvail”.
b. Show that your code satisfies the post-conditions.
c. Show that the class invariant is established at the exit of the method
(Hint: Assume it holds as pre-condition, and prove that it holds as post-condition.)
2.
The ExchangeRate module of a banking system includes a fixed value “poundToLire” equal to
3000, meaning that one pound is equal to 3000 lire, and an operation “calcExchange”. This
operation takes two inputs: amount (of type R for real number) and typeExchange (of type
char) and returns in output newamount. If typeExchange is equal to “L” (i.e. the given amount
was in pounds) then newamount is the equivalent amount in lire. If typeExchange is equal to
“P” (i.e. the given amount was in lire) then newamount is the equivalent amount in pounds.
The operation can only be applicable when typeExchange is equal to “L” or to “P”.
a. Write a class schema ExchangeRate that specifies the above description.
b. Map the class schema ExchangeRate you have given in part (a) into a Java class
ExchangeRate by including the declaration of the data fields of this Java class, with
relevant invariants, the method header and its pre- and post-conditions.
c. Implement the method of the Java class ExchangeRate you have given in part (b),
including suitable mid-conditions, and argue carefully that the code you give satisfies
the post-condition of the.
3.
Suppose that there is a correct implementation of the method Sum specified as follows:
//pre: none;
//post: result = x+y;
int Sum(int x, int y)
a. Write the post-condition and the Java code of the method Average declared below using
a call to Sum.
//pre: x=x0 & y=y0;
//post: YOU FILL IT IN!
int Average(int x, int y)
b. Using the post-condition of Sum, show that your implementation is correct, that is
the post-condition you have defined is satisfied at the exit of the method.
4.
[Thanks to Krysia Broda]
Suppose that there is a correct implementation of the method find, specified as follows:
//pre: none;
//post: (result0  result < a.length  a[result] = x) 
//(result=a.length  j(0 j < a.length  a[j]  x)
int find(int x, int [ ] a)
a. Write the Java code of the method isIn, specified below, using a call to find.
b. Using the post-condition of find, show your implementation of isIn is correct. That is,
that
(i) result=true( i (0 j < a.length  a[i]=x) and
(ii) result = true( i (0 j< a.length  a[i]=x).
//pre: none
//post: result  ( i (0 j < a.length  a[i] = x)
boolean isIn(int x, int [ ] a).
5.
Give the for loop implementation of the method isIn specified in question 4 and describe your
reasoning for showing that the code satisfies the post-condition of isIn. The implementation
should be such that each iteration of the for loop is independent from the others.
6.
Consider a Java method, Eqno, which takes in input two arrays with the same length and
returns the number of elements that they have in common. In other words, the number
count of indexes n such that 0<=n<=(A.length)-1 and A[n]=B[n].
a)
b)
c)
d)
e)
7.
Define the pre- and post-conditions of this method.
The method is to be implemented using a while loop. Draw a diagram of the arrays
A and B, when n elements have been inspected, clearly marking the subscripts for
the last element inspected and the next element to be inspected.
Write a suitable body of the method Eqno in Java, including the loop invariant and
the loop variant.
Prove that the loop code re-establish the loop invariant. That is prove that if the
invariant holds and the loop condition is true before an arbitrary iteration of the loop
then the invariant holds at the end of the loop iteration. Show also that any array
access in this loop is legal, using the loop invariant and the truth of the loop
condition. Show that the loop terminates.
Prove that the invariant is set up at the start of the loop and that the final code
establishes the post-condition.
Repeat the above question for the following two methods:
i)
A method, called Copy, that copies one array of integers A into another array of
integers B given as parameters. Note that the arrays are both passed as parameters
and they have to have the same length.
ii)
A method, called Search, that returns the index of the first occurrence of the given
parameter x in the given array A, if any, or returns A.length if the element x is not in
the array A.
//pre: A is sorted in increasing order;
//post: 0<=result<= A.length  i.((0<=i< result A[i] <x) 
//(result<=i<=A.length-1 A[i]>=x);
int Search(int[ ] A, int x).
M225: SOFTWARE ENGINEERING UNASSESSED
Unassessed Coursework 5: ANSWERS Specifying Java Programs and
While Loops
(1)
(a)
//modifies: balance;
//pre: none;
//post: balance = – limit  result = balance0 + limit;
public int withdrawAvail( ){
// balance = balance0
int tempbalance;
tempbalance = balance;
balance = – limit;
return (tempbalance + limit);
}
(b)
We use backward reasoning to show that the above code proves the given post-condition. Our
post-condition is (balance = – limit)  result = balance0 + limit. We replace result with
tempbalance+limit. This gives us the mid-condition before' (balance = – limit) 
(tempbalance
+ limit = balance0 + limit). Now we replace balance with – limit, and we get the mid-condition
before' (– limit = – limit)  (tempbalance + limit = balance0 + limit), which is logically
equivalent to (tempbalance + limit = balance0 + limit). Now we still proceed backwards and
we replace tempbalance with balance and we get the mid-condition before' (balance + limit =
balance0 + limit), which is equivalent to balance = balance0. This is the information we have
at
the start of the method. Hence the post-condition is satisfied.
(c)
i.e.
(2)
To prove that the invariant is established, we assume that it holds at the entry of the method,
limit  0  balance0 + limit  0. We want to show that it still holds at the end, i.e. limit  0 
balance + limit  0. If we reason backward again, we get the mid-condition, limit  0  (– limit
+ limit  0), just before the operation balance = – limit. This mid-condition before' is
equivalent to limit  0 that is implied by the invariant at the start of the method.
(a)
ExchangeRate
| (CalcExchange)
poundToLire: N
poundToLire = 3000
CalcExchange
typeExchange?: char
amount?: R
newamount!: R
typeExchange? = “L”  typeExchange? = “P”
(typeExchange? = “L”)  newamount! = amount? * 3000
(typeExchange? = “P”)  newamount! = amount? / 3000
(b)
public ExchangeRate {
private final int poundToLire = 3000;
//invariant: poundToLire  0;
public double calcExchange(char typeExchange, double amount)
{//pre: typeExchange = “L”  typeExchange = “P”
//post: ((typeExchange = “L”)  result = amount * poundToLire) 
((typeExchange = “P”)  result = amount / poundToLire);
….}
}
(c)
public double calcExchange(char typeExchange, double amount){
//pre: typeExchange = “L”  typeExchange = “P”
//post: ((typeExchange = “L”)  result = amount * poundToLire) 
((typeExchange = “P”)  result = amount / poundToLire);
// typeExchange = typeExchange0 
// amount = amount0
double temp;
if (typeExchange = “L”)
{temp = amount * poundToLire;}
else { temp = amount / poundToLire;}
// temp = amount0 * poundToLire 
// typeExchange = “L”;
// temp = amount0 / poundToLire 
// typeExchange = “P”;
return temp;
}
To prove that the post-condition is satisfied by the above code we need to show that it is true
independently of the if branch executed. We reason backwards. We replace in the postcondition the value temp instead of result and we get the mid-condition before
((typeExchange = “L”)  temp = amount * poundToLire)  ((typeExchange = “P”)  temp
= amount / poundToLire). We now need to show that this mid-condition is provable from the
mid-conditions given at the end of each of the if and else branches. Consider the if branch.
Since amount is not changed, we have that amount = amount0. Now the mid-condition given in
the code at the end of the if branch implies the first part of mid-condition before given here.
The second part of mid-condition before is automatically true because the antecedent is false.
Analogously for the mid-condition given in the code at the end of the else branch. But in this
case, the first part of the mid-condition before is automatically true because its antecedent is
false. Now we still need to show that our code of the if-then-else statement gives the two midconditions written in the code. This is trivially true. Just reason forward and follow the
instructions. (Note: We have made use of both forward and backward reasoning. You could
have proved it using only the backward reasoning as well).
(3) (a)
//pre: x=x0  y=y0;
//post: result = (x0+y0)/2;
int Average(int x, int y){
int sum;
sum = Sum(x,y);
return sum/2;
}
// x=x0  y=y0;
// sum = x0+y0;
(b) At the beginning, the method Average has the pre-condition x=x0  y=y0. Renaming the
variables, we have just before the call of Sum(x,y) that x 1=x0  y1=y0. The pre-condition of Sum
is none so it’s automatically true. After we call Sum, we have also the post-condition of Sum
rewritten using x1 and y1 instead of x and y. This post-condition says that the result (of Sum) =
x1+y1, which together with the mid-condition just before the call of Sum(x,y) and the operation
sum= Sum(x,y), gives us the mid-condition sum= x0+y0, after this operation. Hence, by returning
sum/2, we get the final mid-condition result = x0+y0/2, which is the post-condition of the
method.
(4) (a) //pre:none
//post: result  ( i (0 i < a.length  a[i] = x)
boolean isIn(int x, int [ ] a){
int y;
y = find(x, a);
return (y < a.length);
}
// x=x0  a=a0;
(b) We show now the two parts of the post-condition of isIn. Assume that  i (0 i < a.length 
a[i]=x). Then, according to the post-condition of find, the call find(x,a) will return a value <
a.length, so the result of isIn will be true as required. Assume now that the result of isIn is true.
Then by the code, we have that y < a.length. Since y is the result of find, the post-condition of
find inforces that a[y]=x. Hence we can conclude that  i (0 i < a.lengtha[i]=x), as required,
by
taking this i equal to y.
(5)
//pre: none
//post: result  ( i: nat. (0 i < a.length  a[i] = x)
boolean isIn(int x, int [ ] a){
boolean isin = false;
// x=x0  a=a0;
for (int i = 0; i < a.length; i++)
if (a[i] = x) isin = true;
return isin;
}
The reasoning is somewhat similar to that given in 4(b). We assume that result = true. Then
for some iteration i < a.length of the for-loop, a[i] = x. Hence  i: nat. (i < a.length  a[i] = x).
Note that all the other iterations are independent from this one and therefore they do not affect
this result. On the other hand, assume that  i: nat. (i < a.length  a[i] = x) in our given
parameter a. Then the for-loop will eventually pass through an iteration, say i, where the check
a[i]=x is true, and so isin = true. Hence result = true, as required. One again, this particular ith
iteration of the for-loop is not affected by the others because they are independent and
therefore
this result of the ith iteration stays the same until the end of the loop.
(6) (a)
//pre: A.length = B.length;
//post: result = number of i such that (0<=i<=A.length-1  A[i]=B[i]);
public int Eqno(int[] A, int[] B)
(b)
elements already inspected
A:
Subscripts:
0 1
B:
Subscripts:
0 1
Count = number of i.
[(0<=i<n  A[i]=B[i]]
n=current
index
A.length-1
B.length-1
(c)
int Eqno(int[ ] A, int[ ] B){
int n = 0;
int count = 0;
// loop_variant: A.length-n; //must decrease
// loop_invariant: 0<=n<=A.length  count = number of i such that
(0<= i < n  A[i] = B[i])
while (n<A.length){
if (A[n] = = B[n]) {count++;}
n=n+1;
}
return count;
}
(d)
To check that the loop invariant is re-established, assume that it is true just before
starting an iteration, and assume that the loop condition is true.
 The current state of the loop body can be characterized as follows. The
expression (0<=n1<=A.length-1)  (count=number of i such that (0 <= i < n1 
A[i]=B[i]) is true. (You should be able to get this definition of the current state
of the loop by just looking at the diagram above.) Moreover, since n1 is in
range for A (as well as for B by the pre-condition), the array accesses A[i] and
B[i] are legal, for each 0 <= i < n1.
 During the execution of the loop body, when n1 is incremented we get n= n1+1,
and (0<=n<=A.length), which is the first part of the invariant. We now check
that the second part is also true. If A[n1]=B[n1] then the number of i such that
(0<= i <= n1  A[i]=B[i]) is one more than the number of i such that (0<= i <
n1  A[i]=B[i]). Using the invariant, this number is count+1. Note that both the
initial values of count and i are incremented together in the loop, so the second
part of the invariant is true. If instead A[n1]B[n1], then the number of i such
that (0<= i <= n1  A[i]=B[i]) is the same as the number of i such that (0<= i <
n1  A[i]=B[i]). By the invariant this is count. So the loop body must in this
case leave count unchanged and then increments n1.
Thus, the invariant is re-established.
The loop will surely terminate, as the variant decreases on each iteration;
initially A.length-n>0 because n=0; at each iteration n increases by one and
A.length-n decreases by one. When A.length-n=0, the loop condition is false,
and the loop terminates.
(e)
The invariant is initially set up as 0<=0<=A.length and as there are no i in the range
0<=i<0, count should be 0, which it is. When the loop terminates we have
n=A.length by the failure of the loop condition and by the invariant. So
count=number of n such that (0<=n<=A.length-1  A[n]=B[n]). As count is returned
as result, the post-condition is set up as well, just substitute result for count in the
last expression: result=number of n such that (0<=n<=A.length-1  A[n]=B[n]),
which is the post-condition.
7i. (a)
//pre: A.length = B.length;
//post: A=B; (i.(0<=i<=A.length-1 => B[i]=A[i]);
(b)
Copied from A to B
A.length-n left to copy
A:
Subscripts:
0 1
n=current
index
A.length-1
B:
Subscripts:
0 1
B.length-1
(c)
public void Copy(int[ ] A, int[ ] B){
int n=0;
// loop_variant: A.length-n; //must decrease
// loop_invariant: 0<=n <=A.length  i.(i < n  A[i]=B[i])
while (n <A.length){
B[n]=A[n];
n=n+1;
}
}
(d)
Let n1 be the value of n just after the loop condition being true (so just before executing
one arbitrary iteration of the while loop). At this point we know that the invariant is
true, so 0<= n1<=A.length-1  i.(i < n1 => A[i]=B[i]). By the end of a loop iteration,
A[n1]=B[n1] also, and n= n1+1. Therefore 0<=n<=A.length i.(i < n => A[i]=B[i]) is
re-established. The loop will surely terminate, as the variant decreases on each
iteration;
initially A.length-n>0 because n=0; at each iteration n increases by one and A.length-n
decreases by one. When A.length-n=0, the loop condition is false, and the loop
terminates.
(e)
Initially k=0, so 0<=0<=A.length and the second part of the invariant is true as its
condition is always false (i.e. there are no i which are 0<=i and i< 0).
At the end of the loop execution, we know that by the invariant k<=A.length and by the
loop test k>=A.length. So k=A.length. By the second part of the loop invariant we have
that i.(i < A.length => A[i]=B[i]) which is our post-condition.
7ii. (a)
pre-conditions and post-conditions are given in the question.
(b)
For i in this range A[i] < x
A:
Subscripts:
0 1
k=current
index
A.length-1
(c)
//pre: A is sorted;
//post: 0<=result<= A.length  i.((0<=i< result A[i] <x) 
(result<=i<=A.length-1  A[i]>=x);
int Search(int[ ] A, int x){
int k=0;
//loop_variant: A.length-k; //must decrease
//loop_invariant: 0<=k<=A.length  i.(i < k  A[i]<x)
while ((k<A.length) && (A[k] <x)){
k++;
}
return k;
}
(d) Let k1 be the value of k just after both loop conditions are true (so just before executing
one round of the while loop). At this point we know that the invariant is true. This
together with the loop condition gives 0<= k1<=A.length-1, A[k1]<x and i.(i < k1 =>
A[i]<x). At the end of the loop code k= k1+1. The invariant is therefore re-established.
The array access in the second loop test is valid also, given that the first loop test
succeeds, so 0<= k1<=A.length-1.
Termination is ensured as the invariant decreases each time around the loop. The
variant is initially >0 and the loop will certainly stop when it is zero. The loop may
terminates sooner if A[k]>=x for some k.
(e)
Initially k=0. The invariant is initially true (argument similar to that given in 7i(e)).
At the end of the loop there are two cases to consider. Either the loop exits because
k>=A.length or because A[k]>=x. In the first case, k=A.length by the invariant, and the
result is correct as the invariant states that all elements of A are smaller than x. In the
second case, k<=A.length-1 and A[k]>=x. Thus when result=k the post-condition is
again established. The first part is by the invariant and the second part is because A is
sorted in increasing order.
Download