Due 05.Mar.29 (Tue)
Before you tackle the homework, remind yourself of our general hw policies (now includes proof-by-induction tips). In particular, you don't need to show your work when only a short answer is required (though doing so might garner more partial credit if the answer is wrong).
Reading: Rosen 3.6, plus some helpful common hw errors from previous years.
x
n
;; pow: real, natnum → real ;; Return xn (where we interpret 00 as 1). ;; (define (pow x n) (pow-help x n 1)) ;; pow-help: real, natNum, real → real ;; Return xn ⋅ soFar (where we interpret 00 as 1). ;; (define (pow-help x n soFar) (cond [(zero? n) soFar] [(odd? n) (pow-help (sqr x) (quotient n 2) (* soFar x))] [(even? n) (pow-help (sqr x) (quotient n 2) soFar)]))Be sure to clearly state which proof method you are using.
sqr
is a function which squares its input.
You may take as given, that (quotient n 2)
returns
n
/2 if
n
even,
and
(n
-1)/2 if
n
odd.
The above procedure is called ``repeated squaring'', and is based on the fact that to compute (for example) 50422, you don't need to do 22 multiplications, but rather just compute 5041, 5042, 5044, 5048, and 50416, and then return 50416⋅5044⋅5042. (Work through a couple of test cases, so that you internalize your understanding of why the algorithm works.)
In a following problem, we visit an iterative version of this same algorithm.
(6pts) (Repeated squaring via side-effects)
Here is an iterative program for repeated-squaring.
(It is the direct
translation2
of the accumulator-style program above.)
// pow: return xn, // where x is any number, n ≥ 0, // and if x=0,n=0 we return 1 // even though NaN would be the mathematically correct answer. // real pow( real x_orig, natnum n_orig ) { real soFar ← 1 natnum n ← n_orig; real x ← x_orig; // This will be x_orig(2i), where i is number of times through loop. // LoopInvariant: …? while (n > 0) { if odd?(n) soFar ← soFar * x n ← quotient(n, 2) x ← sqr(x) // Repeatedly square. } return soFar }Prove this loop correct, using the 4-part proof obligation as in the notes. Your loop invariant will be similar to pow-help's contract, relating x,n,soFar to x_orig,n_orig. (You'll need to include one smidgen more of info in your invariant, to get your proof to go through).
You can assume that your programming language is high-level enough to do arithmetic at the fourth-grade level (add and multiply reals without round-off or overflow error). As last time, be sure to get a feel for what the algorithm is doing, presumably by working through some test cases.
1 Actually, the idiomatic scheme would be to use a ``named-let'' as a loop:
;; pow: real, natnum → real ;; Return x-orign-orig (where we interpret 00 as 1). ;; (define (pow x-orig n-orig) (let looper {[x x-orig] [n n-orig] [soFar 1] } (cond [(zero? n) soFar] [(odd? n) (looper (sqr x) (quotient n 2) (* soFar x))] [(even? n) (looper (sqr x) (quotient n 2) soFar)])))(So really, named-let creates an inner function ``looper'' and calls it with a set of initial-values.) Depending on how aggressive they are on factoring out repeated code (the odd/even case), the seasoned schemer may write:
;; pow: real, natnum → real ;; Return xn (where we interpret 00 as 1). ;; (define (pow x n) (let loop {[x x] [n n] [soFar 1]} (cond [(zero? n) soFar] [else (loop (sqr x) (quotient n 2) (* soFar (if (odd? n) x 1)))])))Alas, this glosses over the contract/description for the inner function, and it's exactly that description which is needed for the proof of correctness.
2
If we weren't trying to preserve the parallel,
I would have named x,n
as xToTwoToI,restOfN resp.
(and left the original formal parameters named x,n).
back