Lesson
The theory โ Binary Search: Four Choices, Sixteen Loops
Binary search finds a value x in a sorted array by keeping a range of indices from lo to hi that must contain x if x is there at all. Each pass reads the middle element, a[mid], and keeps the half on x's side. The range is closed when hi is the last index still in it (lo <= hi, hi starting at n โ 1) and half-open when hi is the first index past it (lo < hi, hi starting at n).
What each symbol means
lo, hi- the ends of the range still to search
mid- โ(lo + hi)/2โ, the index read on this pass
n- the number of elements, at indices 0 to n โ 1
N- the longest array the verdict tests: every n from 0 to N and every target, (N + 1)ยฒ cases
- Assumes
- Different values in the array, a loop that returns as soon as a[mid] equals x, and, in the run and the verdict, integers with no upper limit. The overflow table drops that last assumption and does the arithmetic in 32 bits, as Java does.
- Breaks when
- With repeated values the two correct loops still return an index that holds x, but not necessarily the first. Finding the first, which a range query on a sorted index needs, takes a loop that does not stop on equality: half-open,
hi = midwhenever a[mid] โฅ x, and lo is the answer when the loop ends.
Problem solved in full
-
Why only two loops are left standing 4 steps
Press Stuck at lo = mid. The verdict can test arrays up to 256 elements and no longer. Show, for every n, that the half-open loop with
lo = mid + 1always ends, that the same loop withlo = midsticks whenever hi = lo + 1 and a[lo] < x, and what the same reasoning demands of the closed loop.-
In the half-open loop the body runs only while lo < hi, and then mid, rounded down, is at least lo and strictly less than hi.
-
So
hi = midpulls hi down by at least one, andlo = mid + 1pushes lo up by at least one. Either way hi โ lo, which starts at n, falls by at least one each pass, and it cannot go below zero: the loop ends within n passes. -
Now take
lo = mid. When hi = lo + 1, mid is โ(2lo + 1)/2โ = lo, so if a[lo] < x the update leaves lo where it was and the next pass is the same pass. The preset reaches it at lo = 6, hi = 7, with a[6] = 13 and x = 14. -
The closed loop runs while lo <= hi, so lo = hi is allowed, and then mid = lo = hi: an update to mid changes nothing, so both updates must step past mid, which they can afford because a[mid] has just been read. The half-open range already excludes hi, so there
hi = middrops the element just read and everything above it, all ruled out by a[mid] > x, whilehi = mid โ 1would also drop a[mid โ 1], which may be x.
Answer
Every pass must shrink the range, must throw away only indices that cannot hold x, and the range must start as the whole array. For
lo <= hithat means hi = n โ 1,hi = mid โ 1andlo = mid + 1; forlo < hi, hi = n,hi = midandlo = mid + 1. The verdict confirms the two survivors up to 256 elements, and the argument holds for every n, which is what no test can do. In 32-bit arithmetic it needs one more premise, that lo + hi fits, and the third insight is what happened without it. -
Learning path
Why an algorithm is correct
References (2)
- The overflow in the JDK's binary search, the nine years it went unnoticed, and Bentley's proved-correct version carrying the same line; also the source of the 1946 and 1962 dates the first insight quotes from Programming Pearls: Joshua Bloch, "Extra, Extra - Read All About It: Nearly All Binary Searches and Mergesorts are Broken." Google Research Blog, 2 June 2006.
- The bug report the page reproduces: java.util.Arrays.binarySearch on a 1,100,000,000-byte array, searching for its last element, throwing ArrayIndexOutOfBoundsException: -1064671149: OpenJDK bug JDK-5045582, "(coll) binarySearch() fails for size larger than 1<<30." Filed 11 May 2004 against Java 5.0, resolved (fixed in 6) 28 April 2006.