Maintain opposite signs at the endpoints, rather than assuming that the polynomial is monotone:
def floorRoot(X: Int): Int = {
require(X > 0 && eval(0.0) <= 0.0 && eval(X.toDouble) > 0.0)
var lo = 0
var hi = X
while (hi - lo > 1) {
val mid = lo + (hi - lo) / 2
if (eval(mid.toDouble) <= 0.0) lo = mid
else hi = mid
}
lo
}The loop invariant is and . Both branches preserve those signs, even when there are other roots of a polynomial or sign reversals elsewhere. When the interval width exceeds one, the midpoint is strictly between its endpoints, so the positive integer loop variant
hi - lo strictly decreases. On exit hi == lo + 1, giving the required postcondition . Continuity establishes a root of a polynomial between those adjacent integers, possibly at the left endpoint.The width is at most halved with rounding upwards, so there are at most midpoint evaluations, plus two endpoint evaluations in the precondition check. The midpoint expression avoids adding two large endpoints. The explicit positive upper-bound precondition supplies the interval order assumed in the question. For the literal computed-
eval contract, the same proof holds provided evaluated midpoints are not NaN; identifying its bracket with the real polynomial additionally requires trustworthy evaluation signs.First find a sign bracket by exponential search, then bisect that bracket without evaluating its endpoints again:
def floorRoot(): Int = {
require(eval(0.0) < 0.0)
var lo = 0
var hi = 1
while (eval(hi.toDouble) <= 0.0) {
lo = hi
require(hi <= Int.MaxValue / 2, "doubling bound exceeds Int range")
hi *= 2
}
while (hi - lo > 1) {
val mid = lo + (hi - lo) / 2
if (eval(mid.toDouble) <= 0.0) lo = mid
else hi = mid
}
lo
}For the first while loop the loop invariant is , with
hi a positive power of two, and either or . Every previously tested power of two has nonpositive value. Let be the least nonnegative integer for which . It exists by the positive leading coefficient. If the current upper endpoint is , a mathematical loop variant is while the guard holds; the next doubling decreases it by one. The first while loop's postcondition is . The second while loop has exactly the invariant, variant and adjacent-integer postcondition established in part c.There are evaluations at powers of two. If , the final bracket has width and takes exactly midpoint evaluations. Including the initial sign check, the total is evaluations for , and two for . Thus evaluation cost is for the first positive dyadic bound ; it is not a claim about the first positive root of a polynomial when the polynomial is nonmonotone.
The mathematical argument uses unbounded integer endpoints and reliable signs. The guarded
Int implementation requires the successful dyadic bound to fit in Int; otherwise it explicitly fails rather than wrapping to a negative bound. The coefficient assumptions alone cannot guarantee that a requested integer result fits a fixed-width type. Floating-point overflow or unreliable signs likewise need a wider or certified evaluator for a fully general implementation.Scan the two suffixes until a mismatch or an endpoint:
def longestFrom(i: Int, j: Int): Int = {
require(0 <= i && i <= N && 0 <= j && j <= N)
var k = 0
while (k < N - i && k < N - j && a(i + k) == a(j + k)) {
k += 1
}
k
}The loop invariant is and equality of the two array segments of length . The loop variant is , which decreases whenever the while loop runs. Short-circuit evaluation checks both bounds before indexing. On exit either a string suffix has ended or the next characters differ, so no longer common string prefix is possible. Thus the returned value is the longest common prefix length. Overlap is allowed: the code only reads the array, so the two matching segments need not occupy disjoint storage.
For a finite acyclic linked list define the node sequence, retaining software object identities, byThen scan without changing any links:
def findMin(first: Node): Node = {
require(first != null)
var best = first
var cursor = first.next
while (cursor != null) {
if (cursor.datum < best.datum) best = cursor
cursor = cursor.next
}
best
}At each while loop head, write . The loop invariant says is nonempty,
best belongs to , and its datum is no larger than that of every node in . Initially contains only first; comparing the next node extends the processed string prefix and preserves minimality. The loop variant strictly decreases. At exit the whole list is processed, so the returned reference identifies a minimum node. Ties retain the earliest minimum. Time is linear in the number of nodes and auxiliary space is constant.The precondition is
n != null and , using node identity rather than datum equality. Search for its predecessor:def delete(n: Node): Unit = {
var previous = bagHeader
while (previous.next ne n) {
previous = previous.next
}
previous.next = n.next
n.next = null
}The loop invariant states that
previous is the header or a preceding real node, and . The loop variant is the number of links from previous to n minus one. The precondition guarantees that this search reaches the predecessor before a null dereference. Replacing its successor by n.next bypasses exactly the requested node; clearing the removed node's link detaches it for later reuse. The order and identities of every other node remain unchanged. In abstract terms, only the multiplicity of n.datum decreases, by one. The worst-case time is linear; the pointer modification itself is constant-time.Let and describe the entry state. The postcondition is: the multiset is empty; the returned chain is sorted nondecreasingly; its node sequence is a permutation of . Thus it has exactly the original multiset of data and preserves all duplicates. Strict increase is impossible when the multiset contains repeated values.
def getAllSorted(): Node = {
var head: Node = null
var tail: Node = null
while (bagHeader.next != null) {
val nextSmallest = findMin(bagHeader.next)
delete(nextSmallest)
nextSmallest.next = null
if (head == null) head = nextSmallest
else tail.next = nextSmallest
tail = nextSmallest
}
head
}The loop invariant partitions the original node identities into the remaining multiset and the returned-prefix chain. The latter is sorted, its last node is
tail, and every datum in it is no larger than every remaining datum. Selecting a minimum and appending that detached node preserves all these facts. The loop variant is the number of real nodes remaining in the multiset, decreasing by one. When it reaches zero the string prefix contains the entire original node set, proving the postcondition. An initially empty multiset returns null.This is linked-list selection sort. Its time is in general, from successive scans of lengths , and it uses constant auxiliary space: it relinks original nodes instead of allocating a new node for each item.
Use null to indicate that the requested key is absent:
private def find(name: String, head: Entry): Entry = {
var cursor = head
while (cursor != null && cursor.name != name) {
cursor = cursor.next
}
cursor
}The loop invariant is that the skipped string prefix contains no entry with the requested name and
cursor starts the unsearched string suffix of the unchanged bucket. Its remaining length is a loop variant. Short-circuit evaluation prevents accessing a null entry. On exit, either cursor is null and the whole bucket has been ruled out, or its name equals the key. No list links or stored numbers are modified. String comparison here uses value equality; node deletion elsewhere uses reference identity.Feed each returned output back as the next input to the same channel software object:
class RepeatedChannel(inner: Channel, times: Int) extends Channel {
require(times > 0)
override def apply(n: Int): Int = {
var value = n
var completed = 0
while (completed < times) {
value = inner(value)
completed += 1
}
value
}
}The loop invariant says that
value and the component's current state are exactly those after completed successive applications starting from this invocation's input and the entry state. The loop variant is times - completed. On exit the specified number of applications has occurred; the component is not reset or replaced between them.For input one and a freshly created sum channel, the successive outputs areInitially the component's previous input is zero. The first output is one; on each later call its previous-input state is the input used on the preceding call. Consequently outputs follow the Fibonacci recurrence with initial outputs one and two. The answer is 89, rather than the result of repeatedly supplying the unchanged external input or recreating the component at every iteration. The component retains its final state for later wrapper calls.
Test each positive candidate divisor and count the successful remainder tests:The loop invariant is and , where denotes
def divisors(n: Int): Int = {
require(n >= 1)
var candidate = 1L
var count = 0
while (candidate <= n.toLong) {
if (n.toLong % candidate == 0L) count += 1
candidate += 1L
}
count
}candidate. A successful test adds exactly the new divisor; an unsuccessful test leaves the count unchanged. The loop variant is , which decreases by one. At termination , so the returned value is . A Long loop counter prevents the increment after the last positive Int candidate from wrapping around.Remove each prime power as soon as it is found, and stop trial division once the remaining value has no possible smaller factor:At an outer-loop boundary, write , where is the product of the prime powers already removed. The invariant states that
def divisors(n: Int): Int = {
require(n >= 1)
var remaining = n
var factor = 2
var result = 1
while (factor.toLong * factor.toLong <= remaining.toLong) {
var exponent = 0
while (remaining % factor == 0) {
remaining /= factor
exponent += 1
}
result *= exponent + 1
factor += 1
}
if (remaining > 1) result *= 2
result
}result is , every prime divisor of is smaller than factor, and remaining has no prime divisor smaller than factor. Thus if factor divides remaining, it is prime; a composite factor would have a smaller prime divisor, contrary to the invariant.During the inner loop, with fixed, . Each division removes one copy, so the positive remainder strictly decreases and supplies an inner-loop variant. On leaving the inner loop the complete power has been removed; multiplying the result by
exponent + 1 uses part (d) and reestablishes the outer invariant after advancing factor. The outer factor increases and can never exceed the original while its guard holds, proving termination.At exit, a remainder greater than one must be prime: a composite remainder would have a prime factor no larger than its square root, which would still be smaller than
factor. It therefore contributes one final factor of two. A remainder of one contributes nothing. The algorithm returns using constant auxiliary space and no array. The widened multiplication in the loop guard prevents an Int overflow in the square test.Maintain the required boundary between entries strictly above the target and entries at most the target:The full invariant is , every entry before is greater than , and every entry at or after is at most . The array remains unchanged. Descending order justifies each update: if the middle value is greater, all earlier values are greater too; otherwise all values from the middle onward are at most the target.
def search(u: Array[Int], z: Int): Boolean = {
var a = 0
var b = u.length
while (a < b) {
val middle = a + (b - a) / 2
if (u(middle) > z) a = middle + 1
else b = middle
}
a < u.length && u(a) == z
}The nonnegative loop variant strictly decreases, with each update retaining at most half the old candidate length. On termination , which is the first index whose value could be at most . Equality at that index is therefore necessary and sufficient for presence. Short-circuit evaluation makes the final test safe when , including the empty-array case. The time is , using constant auxiliary space.
def eaFast(n: Int): Double = {
require(n > 0)
var remaining = n
var base = 1.0 + 1.0 / n.toDouble
var result = 1.0
while (remaining > 0) {
if (remaining % 2 == 1) result *= base
base *= base
remaining /= 2
}
result
}Writing , the exact-arithmetic loop invariant is and . For an even integer exponent , replace the base by its square and the exponent by . For an odd integer exponent , first multiply the accumulator by the base and then make the same replacement. Both preserve the product. The loop variant is
remaining, which strictly decreases while positive. At exit the remaining exponent is zero, so the returned value approximates .There are iterations, squarings and one accumulator multiplication for each set bit of : between and multiplications. Thus the bound is as , or including the small case . All power variables are
Double; no unintended Int squaring occurs.Use a
Double base so that the reciprocal is not truncated by integer division. Repeated multiplication gives the requested linear-work approximation to Euler's number.def eaLinear(n: Int): Double = {
require(n > 0)
val base = 1.0 + 1.0 / n.toDouble
var i = 0
var result = 1.0
while (i < n) {
result *= base
i += 1
}
result
}In exact arithmetic over the real numbers, the loop invariant is and . It holds initially, one multiplication establishes the next exponent, and at exit gives the specified power. The nonnegative loop variant is . There are exactly loop multiplications, hence in asymptotic analysis, and one division to form the base. With floating-point arithmetic, the invariant describes the intended exact result; the actual output has the corresponding rounding errors.
Maintain the reciprocal factorial term rather than calculating large factorials separately:
def eb(n: Int): Double = {
require(n > 0)
var i = 0
var term = 1.0
var sum = 1.0
while (i < n) {
i += 1
term /= i.toDouble
sum += term
}
sum
}The exact-arithmetic loop invariant is , , and . It holds for because . Dividing by the new index produces the next reciprocal factorial; adding it extends the partial sum. The loop variant is . At exit the sum includes all terms from through , using exactly divisions and no multiplications in the loop. In particular, the initial
1.0 accounts for the zero-index term. The work bound is , with constant auxiliary space.Avoid forming the enormous powers in the original ratios. Put , and ; algebra givesUse simultaneous exponentiation by squaring to compute the two powers with one loop and a shared remaining exponent:
def ec(n: Int): Double = {
require(n > 1)
val dn = n.toDouble
val a = 1.0 + 1.0 / dn
val b = 1.0 + 1.0 / (dn - 1.0)
var remaining = n - 1
var u = a
var v = b
var pa = 1.0
var pb = 1.0
while (remaining > 0) {
if (remaining % 2 == 1) {
pa *= u
pb *= v
}
u *= u
v *= v
remaining /= 2
}
((dn + 1.0) * a) * pa - dn * pb
}The exact-arithmetic loop invariant is , and . The even integer and odd integer cases preserve both equalities in parallel. The loop variant is
remaining. On termination, the displayed expression is exactly the desired approximation in the ideal arithmetic model. For , the loop uses at most multiplications, followed by three more: multiplications and one loop.Converting
n to Double before adding one also avoids overflowing n + 1 at Int.MaxValue. The algebra avoids power overflow but does not remove catastrophic cancellation: the final subtraction combines quantities of order to obtain a result of order . For sufficiently large , floating-point rounding can outweigh the mathematical truncation error. For example, at n == 1000000 this Double computation returns approximately 2.7180521293, although the exact formula is approximately 2.71828182845916. Arbitrarily many requested decimal digits require increased arithmetic precision, not simply a larger Int argument.def searchUnordered(f: Array[Array[Int]], x: Int): Option[(Int, Int)] = {
val n = f.length
val m = if (n == 0) 0 else f(0).length
var i = 0
while (i < n) {
var j = 0
while (j < m) {
if (f(i)(j) == x) return Some((i, j))
j += 1
}
i += 1
}
None
}The outer loop invariant excludes every entry in earlier rows; the inner one also excludes columns before
j in the current row. Failed equality tests preserve these exclusions. The inner and outer loop variants are and , respectively. A successful return identifies an actual match, while exhaustion excludes the entire array. At most entries are inspected.The source's “at least ” should be read as a worst-case lower bound, using the correct asymptotic direction. If any entry were left unread on an all-nonmatching input, changing only that entry to would leave the observations identical while changing the answer. Thus every always-correct procedure needs inspections in the worst case, and this algorithm achieves entry inspections. Empty dimensions return absence; the asymptotic claim concerns nonempty arrays.
def searchMonotone(f: Array[Array[Int]], x: Int): Option[(Int, Int)] = {
val n = f.length
val m = if (n == 0) 0 else f(0).length
var i = 0
var j = m - 1
while (i < n && j >= 0) {
val v = f(i)(j)
if (v == x) return Some((i, j))
else if (v < x) i += 1
else j -= 1
}
None
}The loop invariant is that any match must lie in rows and columns . Smaller corner values exclude the current row, larger ones exclude the current column, as shown in part (d). The loop variant is nonnegative while the rectangle is nonempty and decreases by one on every unsuccessful inspection. When the loop exits, the rectangle is empty, so absence is established.
At most entries are inspected when both dimensions are positive, giving running time and auxiliary space. An empty dimension causes no entry access. Duplicates do not affect the argument, since every exclusion uses a strict inequality relative to the target.
A loop invariant is a property of the program state that holds whenever control reaches the loop's condition, including the final failed test. Its main purpose is proving partial correctness: establish it initially, prove one guarded iteration preserves it, then combine it with the negated condition to obtain the postcondition. A separate loop variant or another progress argument proves algorithm termination and hence total correctness.
The proposed condition is not a loop invariant at the loop head. It holds initially and on entry to every executed body, but the iteration starting with
i == 10 finishes with i == 11. Control then returns to the condition with the proposed upper bound false. A suitable numerical loop invariant is . If the number of printed lines matters, strengthen it to “exactly lines have been printed.” At exit, this and i > 10 give exactly ten printed lines. The nonnegative loop variant decreases by one on every iteration.This is lower-bound search: return the first position whose value is at least the target, allowing the result to equal the array length. The notation for an empty suffix is vacuously true, so this also covers a target larger than every entry and the empty array. Assume a stable sorted array, ordered nondecreasingly.
def ternarySearch(a: Array[Int], x: Int): Int = {
var lo = 0
var hi = a.length
while (lo < hi) {
val third = (hi - lo) / 3
val m1 = lo + third
val m2 = hi - 1 - third
if (a(m1) >= x) hi = m1
else if (a(m2) < x) lo = m2 + 1
else {
lo = m1 + 1
hi = m2
}
}
lo
}The loop invariant isInitially the excluded prefix and suffix are empty, so both quantified assertions hold. During an iteration, . If , sortedness makes every later value at least , so replacing
hi by m1 preserves the right assertion. If the first test failed and , every earlier value is below , so replacing lo by m2 + 1 preserves the left assertion. Otherwise ; both new exclusions are valid, and lo = m1 + 1; hi = m2 retains exactly the unresolved middle. In that branch , since a single value cannot satisfy both strict and nonstrict inequalities in opposite directions.The nonnegative loop variant
hi - lo strictly decreases. In fact, with old length and , the three possible new lengths are , with the middle branch feasible only when nonnegative; each is at most . At exit lo == hi, and the loop invariant is exactly the desired partition. The result is the unique lower-bound index, including the first equal entry when duplicates occur. This ternary lower-bound search uses comparisons and auxiliary space, in the Big O notation word-operation model. The index formulas also avoid computing an overflowing 2 * length.