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: (result0 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.lengtha[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.