There are a number of frameworks for proving that code is correct. You can read about some of them in books such as
- A practical theory of programming, Hehner, 1993-2014.
- The science of programming, Gries, 1981.
- A method of programming, Dijkstra and Feijn, 1988.
- Programming from specifications, Morgan, 1990, 1994, 1998.
For your program, I'll assume that you don't care about negative n
and that you can handle the n==0
, n==1
, and n==2
cases without any help; I'll only look at the case where n>2
.
The key is to note that (in cases where n > 2
) every time the loop's guard expression divisor <= limit
is evaluated, the following 5 facts are true of the program's state:
n % i != 0, for all i from 2 up to, but not including divisor
limit == n-1
2 <= divisor && divisor <= n
mod != 0 || divisor == 2
n is the same as the argument // i.e. n hasn't been changed
These 5 facts are called the loop's invariant. To trust that the loop's invariant holds where I said it does, you can check
- that it holds the first time the guard is evaluated and
- that, if the invariant is true and the guard is true, then one execution of the loop's body will leave the program in a state where the invariant is true again.
Therefore, when and if the loop terminates normally*, all the above are true and so is divisor > limit
. This tells you that divisor == n
and so you have
n % i != 0, for all i from 2 up to, but not including n
mod != 0
n is the same as the argument // i.e. n hasn't been changed
Therefor, if the loop terminate normally, the return true
after the loop will be executed and the argument is prime.
The only cases left to worry about are those where the loop does not terminate normally. There are two reasons a loop might not terminate normally: it is infinite or there is a jump out of it, such as a break, return, or exception. In your code, the loop can be shown not to be infinite and the only jump out is when n % divisor == 0
. Since, at that point, we also have
2 <= divisor // from the invariant
divisor <= limit // from the guard
limit == n-1 // from the invariant
we have 2<=divisor && divisor < n
. So if the return false
in the loop is executed, it is only when n % divisor == 0
and divisor
is greater than 1
and less than n
, and so the n
(and hence the argument) is not prime.
In summary
- for nonprimes (greater than 2), the loop can not terminate normally and so the
return false
must be executed, and
- for primes (greater than 2) the loop must terminate normally and in a state where
mod != 0
and so the return true
will be executed.
The proof suggests a few ways you could make the code simpler. The books I mentioned provide ways to make the analysis more formal, although I think only Hehner's provides a way to deal with early exits from loops.
[*] By "terminates normally", I mean that it terminates because its guard evaluates to false.
Does this analysis give you absolute certainty that you code is flawless? I hope not. But it should give you reassurance. One can use tools such as ESC/Java2 or Spec# so that a computer can check that one hasn't made any mistakes. That would raise the level of trust that the code is right to a very high level. Of course there could be bugs in the program one uses to check the verification or in the hardware it runs on. For practical purposes code such as yours can be verified and the verification can be checked by carefully written and trustworthy tools.