INFO 150: A Mathematical Foundation for Informatics

David Mix Barrington

Fall, 2017

Lecture #16, 13 October 2017

Induction and Program Correctness

In the last few lectures we have been studying mathematical induction, which for us has been a technique to prove properties of the positive integers. In this lecture we look at cases where these properties involve the behavior of Java methods, using both loops and recursion. By showing that a particular property holds for all positive integer inputs, we will show the method to be correct.

First, though, we need some definitions. To say that a method is correct is to say that it performs according to its specification. The specification typically says that if certain input is given to the method, certain output will result. There may be input that would cause the method to throw an exception or enter an infinite loop, but this does not make the method incorrect if it is never meant to be given that input.

Formally, we have one list of propositions called preconditions and another called postconditions. These might specify the input or output, or define the state of some objects or other aspects of the method's environment. A method is defined to be partially correct if, whenever the preconditions hold before it is run, and it terminnates at all, the postconditions hold after it terminates.

This means that a program that never terminates is always partially correct, with any preconditions and postconditions. We normally make separate proofs, one for partial correctness and one for termination.

Here is a Java method to calculate the remainder when a non-negative integer n is divided by a positive integer b. The preconditions are "a ≥ 0" and "b > 0", and the postconditions are "0 ≤ output < b" and "∃k: n = kb + output".

    int remainder (int n, int b) {
        int x = n;
        while (x >= b) x -= b;
        return x;}

Note that if the preconditions are not true, we might get an output that violates the postconditions (for example, if n = -1 and b = 2) or we might not even terminate at all (for example, if n = 3 and b = -2). But we will be able to show that if the preconditions hold, then the postconditions will hold and the method will terminate.

We'll do this proof by induction on n, assuming that b is some fixed positive integer. The base case is n = 1. What happens when we call the method when n = 1? If b is also 1, the method goes through the while loop once and then exits with x = 0. It terminates with output 0, and this output meets both postconditions. On the other hand, if b > 1, the method returns 1 without going through the while loop at all, and this output also meets both postconditions.

For the inductive case, we assume that the method terminates and meets the postconditions when called with arguments m and b, for any non-negative integer m < n. What then happens when we call the method on n and b? If n < b, it returns n and this meets the postconditions. But if n ≥ b, it enters the while loop and changes x from n to n - b.

At this point, the method is in exactly the same position as it would be if we had called it with inputs n - b and b. In particular, it is at the top of the while loop and x is equal to n - b. By the inductive hypothesis, then, we know that the method will continue until it terminates with an output that meets the postconditions for input n - b and b. So "0 ≤ output < b" and "∃k: n - b = kb + output". The first postcondition is the same for input b, and the second one easily implies the one for input b. So the postconditions we want are true, and we have proved partial correctness.

We can also calculate this remainder by a recursive method:

    int remainder (int n, int b) {
        if (n < b) return n;
        return remainder(n - b, b);}

If we let P(n) be the statement that this new method terminates on input n and b, where b is any positive integer, we can prove P(n) to be true for all n by induction. The base case again breaks into two cases. If b = 1, we make a recursive call with arguments 0 and b, and this returns 0, which meets the preconditions. If b > 1, we return 1, which meets the preconditions.

The inductive case is a bit simpler than before. We again assume that the method terminates with output meeting the postconditions, for input m and b where m is any positive integer less than n. If n < b we return n, which meets the postconditions, without a recursive call. Otherwise we call the method with input n - b and n, and by the inductive hypothesis, this terminates with an output meeting the postconditions for that input. As before, we can also verify that this output meets the postconditions for input n and b.

Here is a method to compute the factorial of a positive integer:


     int factorial (int n);
        if (n <= 1) return 1;
        return n * factorial(n - 1);}

Proving by induction that this method terminates, for any positive integer input, is easy. To prove that it is correct, we need a definition of the factorial function to compare the output against. A nice definition is a recursive one, where we define 1! to be 1 and n! in general to be n * (n-1)!. This is easily shown to be exactly what our recursive method computes. This is one of many examples of recursive definition in mathematics and computing. Induction proofs are particularly well suited to working with recursive definitions.

Finally, let's look at a method that takes a positive integer as input and outputs its prime factorization, by printing it rather than returning it:


    void factor (int n) {
        if (n == 1) return;
        int d = 2;
        while (n % d != 0) d++;
        System.out.println(d);
        factor (n/d);}

If we call this method on input 60, for example, it will print a 2, call factor(30), print another 2, call factor(15), print a 3, call factor(5), print a 5, and call factor(1) which terminates without doing anything. The outputs are all prime numbers, and they multiply together to give 60.

We'll start the inductive proof of correctness for this method, and let you finish it for the exercise. Our statement P(n) will be "on input n, factor terminates and prints a sequence of prime numbers that multiply to give n". P(1) is true because we can see that factor(1) terminates and prints nothing, and the empty sequence of primes multiplies to give 1.

Our inductive hypothesis is that factor(m) terminates, after printing a sequence of primes that multiply to n, for any positive integer m with m < n. Our inductive goal is just the statement P(n).

Exercise: Complete the proof by showing:

Last modified 14 October 2017