[an error occurred while processing this directive]

Reading: Rosen 3.6 (program correctness), 2.5 (first part: integer representations)

Numbers vs Numerals

Number is an abstract idea; Numeral is an encoding of a number as a string. (Roman numerals, base-10 numerals, …). People often confuse a number with a representation of that number. When necessary add brackets and base around a numeral. (And, we'll write numbers as base-10 numerals w/o brackets…) This is the same old semantics/syntax distinction (quoting). (Also reminiscent of aliasing, in programs — you might have one object in your program, but as the program runs there might be several names for it (several variables referring to it).

How to convert? 471 = 4⋅102 + 7⋅10 + 1

What is [471]8 ? [471]8 = 4⋅82 + 7⋅81 + 1⋅80 = 313

How to represent 313 in base 8? We just saw the answer, [471]8. How to calculate this if we didn't know the answer?

313 = 4⋅64 + 57 
    = 4⋅46 + 7⋅8 + 1⋅1
    = [471]8

How to represent the number 313 in binary?

313 = 1⋅256 + 57
    = 1⋅256 + 0⋅128 + 0⋅64 + 1⋅32 + 19
    = 1⋅256 + 0⋅128 + 0⋅64 + 1⋅32 + 1⋅16 + 0⋅8 + 0⋅4 + 1⋅2 + 1⋅1
    = [100 110 011]2
Hmm, note that each group of three binary digits corresponds to a single octal digit.

Separately: Given a decimal number, how do you multiply by 10, or divide by 10? For a binary: What is [110 110 011]2 doubled, and written (again) in binary? What is it halved, rounded down?

This observation leads to a bottom-up way of expressing (say) 41 in binary (generalizes to any base): Note that 41 is odd; what is the last digit of its binary representation? Has to be 1! And, all the other digits will be the representation of 40/2 = 20, since we can take that numeral and append a 1, to get twice 20 plus 1. Repeating the process:

     41 =                         20⋅2   + 1
        =                   (10⋅2+0)⋅2   + 1
        =                  10⋅22 + 0⋅2   + 1
        =              (5⋅2+0)⋅22 + 0⋅2   + 1
        =             5⋅23 + 0⋅22 + 0⋅2   + 1
        =        (2⋅2+1)⋅23 + 0⋅22 + 0⋅2   + 1
        =       2⋅24 + 1⋅23 + 0⋅22 + 0⋅2   + 1
        =  (1⋅2+0)⋅24 + 1⋅23 + 0⋅22 + 0⋅2   + 1
        = 1⋅25 + 0⋅24 + 1⋅23 + 0⋅22 + 0⋅2   + 1
        = 1⋅25 + 0⋅24 + 1⋅23 + 0⋅22 + 0⋅21 + 1⋅20
        = [101001]2.
Thus we can go left-to-right or right-to-left: Either

To yourself, compare each of these algorithms in base 10.

Btw, "bit" comes from "binary digit".

Proofs about programs

;; rdigs->int: (listof real, int) → real
;; Return the number represented by d,
;; a list of digits base b, reversed.
;;
;; Test cases:
;;   (rdigs->int (list 1 7 4))   = 471   ; Using default b=10.
;;   (rdigs->int (list 1 7 4) 8) = 313
;;     ; Since for base 8,     4⋅64 + 7⋅8 + 1 = 313.
;;   
(define/opt (rdigs->int d [b 10])
  (cond [(empty? digs) 0]
        [(cons?  digs) (+ (first digs) (* b (rdigs->int (rest d) b)))]))
;
;  Note: for allowing optional arguments in scheme via "define/opt", 
;   see the teachpack-demo page,
; http://www.owlnet.rice.edu/~comp210/02fall/Handouts/teachpack-demo.shtml
; and follow the link at the bottom of the page for "optional arguments".

Let rd2i be the function: rd2i(d,b) = ∑i=0n-1 (di ⋅ bi)
where n is d.length() and d = (list d0 d1 d2 … dn-1 dn)

Proof goal: For a list of digits d, let P(d) be "(rdigs->int d b) = rd2i(d,b) = ∑i=0n-1 (di ⋅ bi)" (for arbitrary b>0).
Note that we are relating program-outputs with a mathematical function.

Proof, by structural induction on d.

In retrospect, we could break this proof into two parts: in the math domain, show that rd2i as defined by the summation is the same as rd2i defined recursively; then in the program domain (which mirrors the recursive def'n of rd2i) we just leverage off the previous result. Thus the above could all be re-stated:

Lemma: For d a non-empty list of digits, rd2i(d) = (first d) + b⋅rd2i((rest d)). Proof of lemma:

      rd2i(d)                                           
    = ∑j=0n-1 dj⋅bj                      ; By def'n of rd2i.

    = d0⋅b0 +   ∑j=1n-1dj⋅bj             ; Sum notation.
    = d0    +   ∑j=1n-1dj⋅bj)            ; 
    = d0    + b⋅∑j=1n-1    dj  ⋅bj-1))   ; Factor out a b.
    = d0    + b⋅∑i=0(n-1)-1 di+1⋅bi))     ; Re-index[j/i+1].
    = d0    + b⋅rd2i((rest d))))        ; By def'n of rd2i.

Proof of P(d) = "rd2i(d) = (rdigs->int d)", by structural induction on d (a list of digits).

Reached here 04.mar.18, having also finished up structural induction.
An iterative version:
/* rdigs->int:
 * rdigs->int: (listof real, int) → real
 * Return the number represented by d,
 * a list of digits base b, reversed.
 *
 */
real rdigs->int( (listof real) d, int b=10 ) {
  real sumSoFar ← 0;
  for (int i ← 0; i < d.length(); i ← i+1 )
    sumSoFar ← sumSoFar + (list-ref d i) * pow(b,i);
  return sumSoFar;
  }

/* Test cases:
 *   List A ← new Cons( 1, new Cons( 7, (new Cons( 4, theEmptyList))));
 *
 *   // Using default b=10:
 *   unless (rdigs->int(A) == 471)     { println("Test case 1 failed."); };
 *   // Since for base 8,     4 + 7*8 + 1*64 = 313:
 *   unless (rdigs->int(A, 8) == 289)  { println("Test case 2 failed."); };
 */

These statements are about program variables (though we have yet to say at which moment in time we are talking about these variables).

Here's a new rule of inference requiring four premises (our "proof obligation"):

Lemma: d = d′; b = b′.
Corollary: d.length() = d′.length(); d.i = d′.i for all i

Show:

When writing proofs, it's not unusual to discover snags, and need to go back and patch them up; your first write-up is rarely your last. You're encouraged to write up your proofs in a word processor, to avoid having to re-copy your homework.

The Structure of Loop Proofs

Reflecting a moment, on the style of proof: Programs that respect the intrinsic structural definition of their data (the "natural recursion" of Comp210) are amenable to proofs by induction. Programs which need to change state are harder to reason about (partly because our mathematics doesn't naturally reaon about time and state (change-over-time). Especially when the state is spread out over different functions (and if a function doesn't document what state it's modifying, things are bleak indeed). Such code is not only harder to reason about, it tends to be far more bug-prone.

A good programmer is aware of when a functional (state-free) approach is good, and when keeping state is appropriate; this is an important distinction you probably didn't realize you were learning in Comp210/How to Design Programs.

Just as the purpose of loop constructs is to provide a high-level way of separating ``do a task once'' from ``repeat the task as needed'', our proof framework is separating ``show that one iteration is correct'' from ``show that it's repeated as needed''. Our proof structure is reflecting the structure of the underlying program. Like induction, it gives us a rule of inference so that we don't have arguments which include a hand-wavy ``dot dot dot'' or ``and hey this keeps repeating until gee it must be giving the right answer''.

When to use primes?

If x is a variable in the program loop, our proof's ``x'' is the value at the start of the loop body. The primed version x′ is the value at the end of the loop body. When we talk of L′, we mean ``the statement like L, but with all variables primed''. In particular, our loop invariant L never mentions primed variables — it is the proof framework where we consider both L and L′.

More examples: The other direction

;; int->rdigs: integer, [int] → (listof int)
;; Return a list of digits base b, reversed.
;;
(define/opt (int->rdigs n [b 10])
  (cond [(zero?     n) empty]
        [(positive? n) (cons (remainder n b) (int->rdigs (quotient n b) b))]))

; Test cases:
;
(int->rdigs 471)   = (list 1 7 4)     ; Using default b=10.
(int->rdigs 289 8) = (list 1 7 4)
  ; Since for base 8,     4 + 7⋅8 + 1⋅64 = 289.
  ; (Hmm, 289 = 172 and 22 = 4, hmm 1,7,4… Numerology is real!)

Prove: rd2i((int->rdigs n b), b) = n.
Proof by strong induction(*) on n:

(*)This proof by strong induction is kinda a proof by (regular ol') math induction on the number of base-b digits of n. In fact, that's the essence of what we're proving.

Now let's show the same thing for an imperative version. This version, unlike the above, is tail-recursive (i.e., corresponds to an accumulator-style program).

List int2digs( int n, int b = 10 ) {
  List soln = theEmptyList();

  while (n > 0) {
    soln ← new Cons( remainder(n,b), soln );
    n ← quotient(n,b)
    }
  return soln;
  }

What is the statement of correctness? What will the loop invariant be?

attempt 1

Trying to phrase these is difficult; we'll tweak our program so that these things can be stated better.

List int2digs( int n, int b = 10 ) {
  List soln = theEmptyList();
  int m = n;
  int loops = 0;   // A dummy variable to help state loop invariant.
                   // Just counts how many times we've executed the loop.

  while (m > 0) {
    soln ← new Cons( remainder(m,b), soln );
    m ← quotient(m,b)
    loops ← loops + 1;
    }
  return soln;
  }

/* Test cases:
 *
 * unless ((int->rdigs(471)).equals( new Cons( 4, new Cons( 7, new Cons( 1, theEmptyList()))) )) {
 *   printf("test case failed: int->rdigs(471) == %s.", int->rdigs(471).tostring());
 *   };
 *
 * unless ((int->rdigs(471,8)).equals( new Cons( 3, new Cons( 1, new Cons( 3, theEmptyList()))) )) {
 *   printf("test case failed: int->rdigs(313) == %s.", int->rdigs(313).tostring());
 *   };
 *
 */

It turns out, this loop invariant can be shown but takes a lot of work (since it deals with taking the quotient(n,b) loops times and then remainder(m,b) once, proving that this is the loops digit of n.)

Attempt 2

Looking for a better loop invariant: If we consider the accumulator (tail-recursive) version, what is the contract for this function? We'll use this!

(define/opt (int->rdigs n [b 10])
  (local {;; help: int, (listof digits) → list-of-digits
          ;;   [What purpose statement?]
          (define (help n digsSoFar)
             (cond [(zero? n)     digsSoFar]
                   [(positive? n) (help (quotient n b)
                                        (cons (remainder n b) digsSoFar))]))}
    (help n empty)))
The Purpose statement for "help" — defining what it returns in terms of its inputs n, digsSoFar — is:
;; help: returns the reversed list of digits representing n (base b),
;;       appended with digsSoFar.
This formulation gives us the insight for a what more manageable loop invariant can be (after all, the accumulator helper-function was really just a loop). The loop invariant L is:
n = m⋅b(length digsSoFar) + d2n(digsSoFar). and b>1 and n≥0.
(note how n is always the same value in this program.)

Lemma: d2n( (cons x l), b) = x⋅blength(l)+d2n(l)
since d2n is the digits→numbers function where digits are in descending order. [subscripted proof omitted, for this plain-text.]

Lemma: for any x≥0, d>1, we have:
n = (quotient n d) ⋅ d + (remainder n d) . This is a property of quotient and remainder

List int2digs( int n, int b = 10 ) {
  List digsSoFar = theEmptyList();
  int m = n;

  while (m > 0) {
    digsSoFar ← new Cons( remainder(m,b), digsSoFar );
    m ← quotient(m,b)
    }
  }

Other variants: this same thing, but with accumulator versions. These give rise to versions, that don't reverse the list of digits.

Write the code for the iterative non-reversing version. What is the loop invariant? This can be hard to get right; my first try gave something that required a scad of intricate, boring math details (but finally did work).

A better way to get the invariant came from first examining a tail-recursive (accumulator) function, and stating its contract.

--------- Sample exercise --------

Not an interesting program, but one to test your knowledge: Prove the following code correct:

int sumFrom( int a, int b )
  sum ← 0
  i   ← a
  while (i < b)
    sum ← sum+i
    i   ← i+1
  return sum

[First you'll need to add comments saying what this function does, and then you'll need to phrase its purpose in terms of functions, summation-notation, sets, whatever -- something that we can reason about mathematically.]

solution [an error occurred while processing this directive] [an error occurred while processing this directive]