You are on page 1of 22

CORRECTESS

selinao@uonbi.ac.ke
Introduction to Correctness
• An algorithm for a computational problem is correct if, for
every legal input instance, the required output is produced
• examples of correctness
– an algorithm to compute the maximum of two numbers is
correct if it returns the larger of the two numbers
– an algorithm to sort a list of numbers is correct if it returns a
permutation of the list in which each element is =< its successor
– an algorithm to return the index of the largest element in an
array A of n numbers is correct if it returns an index i, 1 =< i =< n
such that there is no j 1 =< j =< n A[i] < A[j]
Partial vs total correctness
• it is conventional to distinguish between
partial and total correctness
• an algorithm that produces the desired output
for all inputs when the algorithm terminates is
said to be partially correct
• an algorithm that produces the desired
output for all inputs and is guaranteed to
terminate is said to be totally correct
Why correctness is important
• all code should be correct, but the amount of effort
we spend establishing correctness is context
dependent:
– coursework submission: loss of marks
– mission critical application: loss of business, reputational
damage, inability of an organisation to function, ...
– safety critical application: loss of life
• cf efficiency: all code should be efficent, but the
amount of effort we spend speeding it up is context
dependent
Correctness vs testing
• correctness is not the same as testing
• testing is applied to an implementation of an algorithm
– we need something runnable in order to perform the
tests
• each test shows the implementation is correct for a
particular input and output
• many algorithms have an infinite number of possible
inputs, but we can only run a finite number of tests
• testing can increase confidence that there are no bugs,
but we cannot show an algorithm is correct by testing
Correctness vs testing
• correctness is (usually) a property of an
algorithm rather than an implementation of
the algorithm
– we can show an algorithm is correct before we
have an implementation
– caveat: for some safety critical applications, the
implementation may also be proved correct
• a correct algorithm produces the correct
output for all (legal) inputs
Why bother testing?
• an implementation of a (correct) algorithm may contain bugs, e.g., due
to programmer errors or converting an abstract algorithm into the
syntax and data structures of a particular implementation language
• why not prove the implementation correct?
• the techniques required to prove correctness require effort, and this
effort increases as the level of abstraction reduces
• proving an implementation correct may be infeasible or not worth the
effort
• generally, we prove an algorithm correct (once), and rely on
compilation time checks, assert statements, unit tests etc. to provide
some level of assurance that each implementation of the algorithm is
correct
Showing an algorithm is correct
• to show an algorithm is correct for all (infinitely
many) legal inputs, we must reason formally
about the steps in the algorithm
• essentially, we construct a proof that for any
legal input, the algorithm terminates and
produces the correct output
• we have to do this using only the properties of
the inputs and outputs, rather than particular
values (as there are infinitely many values)
Showing an algorithm is correct
• to show an algorithm is correct for all (infinitely
many) legal inputs, we must reason formally about
the steps in the algorithm
• essentially, we construct a logical proof that for any
legal input, the algorithm produces the correct output
• we have to do this using only the properties of the
inputs and outputs, rather than particular values (as
there are infinitely many values)
• focus on proof (assertion) based approaches
Assertions
• An assertion is simply a precise formal
statement about what holds at a particular
state in a computation
• often specified in predicate calculus, however
other (semi)formal notations are also used,
e.g., mathematical notation [we will use both]
• An assertion is a partial description of a
computational state
General approach
• State a sequence of assertions that are true at
particular points in the computation
• Show, by formal reasoning, that if the ith assertion
holds, the i+1th assertion must also hold
• First assertion (precondition) corresponds to the (legal)
input
• Last assertion (postcondition) corresponds to the
output
• N o one correct way to do this – defining assertions and
finding a proof is a creative process (like algorithm
design)
Iterative algorithms
• Assertions are (relatively) easy to use for non-
iterative algorithms, e.g., finding the maximum
of two numbers
• Correctness of iterative algorithms (algorithms
containing loops) is a bit more challenging
– assertions about loops are harder to formulate –
the assertion must hold for all iterations of the
loop
• we need to worry about termination
Loop invariants
• Assertions about loops are called loop
invariants
• A loop invariant states important relationships
between variables (typically those updated by
the loop)
• Invariant must be true:
– At the start of every iteration of the loop; and
– when the loop terminates
Which invariant?
• In general, there will be many true loop
invariants for a given loop
• Many of these invariants will be trivial, or not
useful in proving the algorithm correct
• Key is to find an informative invariant that
helps prove correctness of the algorithm as a
whole
Example: An uninformative invariant
• FindMax(L)
<precondition: L is an array of n > 0 integers>
i=1
j=1
while(j =< n)
<invariant: 1 =< j =< n >
j++
if(L[i] < L[j])
i=j
<postcondition: i is any index with maximum value in L>
return i
Example: an informative invariant
• FindMax(L)
<precondition: L is an array of n > 0 integers>
i=1
j=1
while(j =< n)
<invariant: L[i] is max in L[1 … j]>
j++
if(L[i] < L[j])
i=j
<postcondition: i is any index with maximum value in L>
return i
Establishing a loop invariant
• We need to show two things about a loop invariant
– initialisation: the invariant is true prior to the first iteration of
the loop
– maintenance: if the invariant is true before an iteration of the
loop, it remains true before the next iteration
• If these two properties hold, the invariant is true prior to
every iteration of the loop and when the loop terminates
(if it does)
• Typically, we use the code in the loop body (or the
corresponding assertions if the code is complex) to prove
that the invariant remains true before each iteration
Establishing initialisation
• We use the assertions and code before the loop to show
the invariant is true prior to the first iteration of the loop
• for example
<precondition: L is an array of n >0 integers>
i=1
j=1
<invariant: L[i] is max in L[1 … j]>
• since L[1 ... j] is a subarray containing a single element
(since j = 1), L[i] is trivially max in L[1 ... j]
Establishing maintenance
• We use the code in the loop to show that the invariant remains
true at the beginning of the next iteration of the loop
• for example, if the invariant is true before the loop body is
executed
<invariant: L[i] is max in L[1 … j]>
j++
if(L[i] < L[j])
i=j
• Then after the loop (before the next iteration) the subarray L[1
… j] contains one more element, and if the new element L[j] >
L[i] then i is reset to j, so L[i] is still max in L[1 … j]
Loop invariants & correctness
• Loop invariants can be used to prove partial
correctness
– an algorithm that produces the desired output for all
inputs when the algorithm terminates is said to be
partially correct
• To prove total correctness
– an algorithm that produces the desired output for all
inputs and is guaranteed to terminate is said to be
totally correct
• we also need to prove termination
Putting it all together
• To show the correctness of a simple iterative algorithm
(containing a single loop)
• Use the precondition and code before the loop to show
the invariant is true prior to the first iteration of the loop
• Use the code in the loop to show that the invariant
remains true at the beginning of the next iteration of the
loop if necessary, prove the loop terminates
• Use the loop invariant & loop termination condition and
code after the loop to show the postcondition holds
Correctness of Insertion Sort

You might also like