
Loop Invariants - kercker
http://www.cs.miami.edu/home/burt/learning/Math120.1/Notes/LoopInvar.html
======
justinsaccount
> If the current comparision shows a smaller value, we update smallestSoFar.
    
    
      if ( a[smallestSoFar] > a[nextToCheck] ) {
    
    

This code makes me twitch. Finding a smaller value by checking to see if the
old value is larger than the new value... Instead of checking to see if the
new value is smaller than the old value.

Additionally, calling the value you are _currently_ checking 'nextToCheck' is
also confusing. It's not the next one, it's the current one.

~~~
jjaredsimpson
a > b is the same as b < a and I would argue a > b is clearer in this instance
by noting the symmetry of the following line:

    
    
        if ( a[smallestSoFar] > a[nextToCheck] ) {
          smallestSoFar = nextToCheck ;
        }
    

When a > b then a = b

The order of the variables doesn't change.

The value is called nextToCheck because the article is specifically talking
about loop invariants. The value of nextToCheck only makes sense when loop
invariant is established prior to the loop and after work has been done in the
loop to reestablish the loop invariant.

In both of those cases the value in nextToCheck is clearly meant to denote the
half open interval [0, nextToCheck) which contains the smallestSoFar.

currentComparisonIndex is not the correct name for the loop variable because
that name is only accurate after checking the termination condition and prior
to incrementing. This is not what the loop invariant states the purpose of
nextToCheck is. Therefore you would not have established a proper loop
invariant prior to entering your loop. nextToCheck might more fully be called
'nextToCheckAfterTerminationCheck'

Of course this is all meta above the level of the language is self. You could
call the variable 'foo' and of course the code would still function.

~~~
justinsaccount
It must be a mathematician thing. When I am doing something that involves
finding a smaller number, I expect to see a < operator.

How would this look if you were writing code that dealt with something like a
linked list where you have 'cur' and 'next' pointers? 'cur' would be 'next'
and 'next would be 'nextNext' ?

~~~
GFK_of_xmaspast
As a mathematician, that code snippet seems perfectly fine?

~~~
justinsaccount
I meant that as in, I am not a mathematician.

When I am looking at code that is trying to find a smaller number I expect to
see a < operator.

------
PaulHoule
One of the things I find funny is that the halting problem is the least of my
concerns working in mainstream programming languages today.

Maybe back in the day when people were using basic and assembly language it
was easy to write some gawdawful mess but since structured programming I think
it is easier to do the right thing rather than the wrong thing. Partially
because we have good constructs to work with, secondly they are good
constructs to reason with -- for instance, you might have the idea of a loop
invariant in your head when you write the loop, even if you don't call it
that.

~~~
ufo
The halting problem is just an example. The real concern is that any
"nontrivial" program property is also undecidable (see Rice's Theorem)

~~~
Animats
That's incorrect. Rice's theorem only says that it is possible to construct
programs for which "nontrivial" program properties are undecidable. But you
don't _have_ to construct undecidable programs. In fact, you have to work at
it to write a useful but formally undecidable program. (Also, you need
infinite memory to get theoretical undecidability from a deterministic system.
With finite memory you eventually have to halt or repeat a state.)

The people behind the Microsoft Static Driver Verifier say that they are
unable to decide if a program is safe to be a kernel driver about 4% of the
time. This is grounds for rejecting the driver. If your kernel driver is
anywhere near the limits of formal decidability, it shouldn't be in the
kernel.

Typically, you fix undecidability by adding loop counters or checking. If you
have some complex algorithm where termination is hard to prove, adding a loop
counter and an iteration limit will guarantee termination. (This comes up as a
practical matter in floating point work. There are algorithms which are
guaranteed to terminate on real numbers, but not on finite-precision floating
point numbers. One example I've run into is GJK, used in collision detection.)

Undecidability is just not a problem in real program verification.

~~~
ufo
I wasn't trying to imply that formal verification can't work around
undecidability in practice. But you are correct in saying that you can often
avoid undecidability by restricting yourself to certain idioms, type systems,
etc.

------
tmerr
Hey, this seems like an application of induction. You choose some property you
want to prove about your program, show it is true before the loop (base case),
then show it remains true for any next iteration (inductive step), therefore
it is true for all iterations.

It reminds me of the hand-wavy inductive proofs I run through my head when I
write recursive functions. Usually it's not even about eliminating bugs, it's
just the most natural way to understand why the code works. It seems less
natural for imperative for/while loops but still a good tool to keep in mind.

~~~
contravariant
You can view it as induction, although the general case is slightly different.
In the general case you have a while loop with some condition and invariant,
and after the while loop stops your invariant is true and the condition is
false, if you've done things correctly this then implies your 'goal'.

So to find a minimum in a list A[1],A[2],...,A[n] you can something like:

    
    
        i := 1;
        // Invariant: A[min] is the smallest in {A[1], ..., A[i]}
        while not i = n do begin
            if A[i+1] < A[min] then min:= i+1;
            i:= i+1;
        end
    

The negation of the condition: 'i = n', combined with the invariant: 'A[min]
is the minimum of {A[1], ..., A[i]}', gives you the result you wanted.

------
yodsanklai
As a source of motivation for students, does anyone could point me to some
"real (open source) code" where the programmer commented a loop with an
invariant for clarity.

~~~
dilap
Here's binary search from Go's sort package:

    
    
        func Search(n int, f func(int) bool) int {
            // Define f(-1) == false and f(n) == true.
            // Invariant: f(i-1) == false, f(j) == true.
            i, j := 0, n
            for i < j {
                h := i + (j-i)/2 // avoid overflow when computing h
                // i ≤ h < j
                if !f(h) {
                    i = h + 1 // preserves f(i-1) == false
                } else {
                    j = h // preserves f(j) == true
                }
            }
            // i == j, f(i-1) == false, and f(j) (= f(i)) == true  =>  answer is i.
            return i
        }
    

There's lots more examples of you search for "invariant" thru the std library
source code.

A few, to give a taste:

ast/print.go printer.Write

    
    
        // invariant: data[0:n] has been written
    

printer/printer.go trimmer.Write:

    
    
        // invariants:
        // p.state == inSpace:
        //  p.space is unwritten
        // p.state == inEscape, inText:
        //  data[m:n] is unwritten
    

path/filepath/path.go: path.Clean

    
    
        // Invariants:
        //  reading from path; r is index of next byte to process.
        //  writing to buf; w is index of next byte to write.
        //  dotdot is index in buf where .. must stop, either because
        //      it is the leading slash or it is a leading ../../.. prefix.
    

Invariants really are a really useful way to think about code.

~~~
ufo
Binary search is one of my favorite examples for introducing people to loop
invariants. There are tons of opportunities to mess up with an off-by-one
error but if you state the invariant beforehand getting it right is super
easy. There are also many variations of the binary search problem so you can't
just memorize the implementation.

------
mhd
This reminds me of Eiffel, where loop (in)variants had to be explicitly
declared, as part of its design-by-contract dogma.

~~~
phpnode
I wrote a Babel plugin which brings a similar syntax to JavaScript, so you can
write this:

    
    
        for (let i = 0; i < someVar; i++) {
           invariant: i < 10000; 
           console.log(i);
        }
    

along with other kinds of contract, like preconditions and postconditions -
[https://github.com/codemix/babel-plugin-
contracts](https://github.com/codemix/babel-plugin-contracts)

------
Rhapso
If I made a zeno's paradox loop:

    
    
      bigFloat delta = 0.5
      bigFloat total = 0.0
      while total < 1.0:
           total+=delta
           delta*=0.5
    

Where does this fit in the model of using a loop invariant to show the loop
terminates? The loop is always approaching the terminal case (and eventually
the machine runs out of memory)

~~~
SixSigma
delta > epsilon

when this is false, the loop should terminate

even BigFloats have limited precision

~~~
spacehome
I think this depends on implementation. I don't know exactly what the
grandfather means by BigFloat, but by analogy with BigInteger, I assume it's
arbitrary precision.

~~~
SixSigma
Arbitrary means the epsilon is chosen, not unbounded.

I have just also realised that delta would be 0 if < epsilon. So the invariant
delta > 0, break when false. (I think, the OP is my introduction to invariant
loops).

Scheme, for instance, has a 32 bit mantissa for BigFloat by default [1]

Haskell's mantissa precision is determined by the type of the mantissa when
created [2]

For GNU MPFR "the precision in bits can be set exactly to any valid value" [3]
which is what is used in Lisp, Python, Perl, Racket, Java, ADA, Ruby & Eiffel.

[1]
[https://github.com/55portal55/bigfloat/blob/master/bigfloat....](https://github.com/55portal55/bigfloat/blob/master/bigfloat.scm)

[2]
[https://hackage.haskell.org/package/numbers-3000.2.0.1/docs/...](https://hackage.haskell.org/package/numbers-3000.2.0.1/docs/Data-
Number-BigFloat.html)

[3] [http://www.mpfr.org/mpfr-current/mpfr.html](http://www.mpfr.org/mpfr-
current/mpfr.html)

~~~
spacehome
> Arbitrary means the epsilon is chosen, not unbounded.

This is not the commonly understood nomenclature, notwithstanding what some
languages call a BigFloat. See: [https://en.wikipedia.org/wiki/Arbitrary-
precision_arithmetic](https://en.wikipedia.org/wiki/Arbitrary-
precision_arithmetic)

In any case, it's clearly not what Rhapso intended, either, because otherwise
his code snippet wouldn't exhaust memory.

~~~
Rhapso
Inretrospect it would have been more fun to say:

    
    
       BigRational delta

------
mrestko
A few more examples would have been nice.

