Numeric binary search
Binary search also works on continuous ranges. Instead of an index window you keep a real-valued interval and halve it until it is narrow enough, which is called bisection.
Learning goals
- Run bisection on a real interval to approximate a root
- Choose a stopping rule: epsilon width or a fixed iteration count
- Understand why floating-point comparisons need care
Prerequisites
How do you compute √2 with only multiplication? Guess the middle of [1, 2], check whether its square is too big, and halve the interval. Twenty halvings give six correct digits.
Floating-point bisection
Keep lo and hi such that the answer is always between them. Take mid = (lo + hi) / 2, test it, and replace either lo or hi with mid. Unlike the integer version there is no + 1, because there is no next value to step to.
Change the epsilon in the animation and see how the number of iterations grows.
Worst
O(log((hi - lo) / eps))
Space
O(1)
Square root by bisection
Interactive · edit the inputAnswer space [0, 10]
√10 is somewhere in [0, 10]. hi is at least 1 so the range also covers x < 1.
1public static double sqrt(double x, double eps) {2double lo = 0, hi = Math.max(1, x);3while (hi - lo > eps) {4double mid = (lo + hi) / 2;5if (mid * mid < x) {6lo = mid;7} else {8hi = mid;9}10}11return (lo + hi) / 2;12}
Live state
x
10
eps
0.01
lo
0
hi
10
Execution trace (dry run table)Show 33 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L2 | x=10 eps=0.01 lo=0 hi=10 | √10 is somewhere in [0, 10]. hi is at least 1 so the range also covers x < 1. |
| 2 | L3 | x=10 eps=0.01 lo=0 hi=10 width=10 | The interval is still wider than eps. |
| 3 | L5 | x=10 eps=0.01 lo=0 hi=10 mid=5 mid*mid=25 | mid is at or above the root, the root is to the left. |
| 4 | L8 | x=10 eps=0.01 lo=0 hi=5 | hi = 5. |
| 5 | L3 | x=10 eps=0.01 lo=0 hi=5 width=5 | The interval is still wider than eps. |
| 6 | L5 | x=10 eps=0.01 lo=0 hi=5 mid=2.5 mid*mid=6.25 | mid is too small, the root is to the right. |
| 7 | L6 | x=10 eps=0.01 lo=2.5 hi=5 | lo = 2.5. |
| 8 | L3 | x=10 eps=0.01 lo=2.5 hi=5 width=2.5 | The interval is still wider than eps. |
| 9 | L5 | x=10 eps=0.01 lo=2.5 hi=5 mid=3.75 mid*mid=14.0625 | mid is at or above the root, the root is to the left. |
| 10 | L8 | x=10 eps=0.01 lo=2.5 hi=3.75 | hi = 3.75. |
| 11 | L3 | x=10 eps=0.01 lo=2.5 hi=3.75 width=1.25 | The interval is still wider than eps. |
| 12 | L5 | x=10 eps=0.01 lo=2.5 hi=3.75 mid=3.125 mid*mid=9.76563 | mid is too small, the root is to the right. |
| 13 | L6 | x=10 eps=0.01 lo=3.125 hi=3.75 | lo = 3.125. |
| 14 | L3 | x=10 eps=0.01 lo=3.125 hi=3.75 width=0.625 | The interval is still wider than eps. |
| 15 | L5 | x=10 eps=0.01 lo=3.125 hi=3.75 mid=3.4375 mid*mid=11.81641 | mid is at or above the root, the root is to the left. |
| 16 | L8 | x=10 eps=0.01 lo=3.125 hi=3.4375 | hi = 3.4375. |
| 17 | L3 | x=10 eps=0.01 lo=3.125 hi=3.4375 width=0.3125 | The interval is still wider than eps. |
| 18 | L5 | x=10 eps=0.01 lo=3.125 hi=3.4375 mid=3.28125 mid*mid=10.7666 | mid is at or above the root, the root is to the left. |
| 19 | L8 | x=10 eps=0.01 lo=3.125 hi=3.28125 | hi = 3.28125. |
| 20 | L3 | x=10 eps=0.01 lo=3.125 hi=3.28125 width=0.15625 | The interval is still wider than eps. |
| 21 | L5 | x=10 eps=0.01 lo=3.125 hi=3.28125 mid=3.20313 mid*mid=10.26001 | mid is at or above the root, the root is to the left. |
| 22 | L8 | x=10 eps=0.01 lo=3.125 hi=3.20313 | hi = 3.20313. |
| 23 | L3 | x=10 eps=0.01 lo=3.125 hi=3.20313 width=0.07813 | The interval is still wider than eps. |
| 24 | L5 | x=10 eps=0.01 lo=3.125 hi=3.20313 mid=3.16406 mid*mid=10.01129 | mid is at or above the root, the root is to the left. |
| 25 | L8 | x=10 eps=0.01 lo=3.125 hi=3.16406 | hi = 3.16406. |
| 26 | L3 | x=10 eps=0.01 lo=3.125 hi=3.16406 width=0.03906 | The interval is still wider than eps. |
| 27 | L5 | x=10 eps=0.01 lo=3.125 hi=3.16406 mid=3.14453 mid*mid=9.88808 | mid is too small, the root is to the right. |
| 28 | L6 | x=10 eps=0.01 lo=3.14453 hi=3.16406 | lo = 3.14453. |
| 29 | L3 | x=10 eps=0.01 lo=3.14453 hi=3.16406 width=0.01953 | The interval is still wider than eps. |
| 30 | L5 | x=10 eps=0.01 lo=3.14453 hi=3.16406 mid=3.1543 mid*mid=9.94959 | mid is too small, the root is to the right. |
| 31 | L6 | x=10 eps=0.01 lo=3.1543 hi=3.16406 | lo = 3.1543. |
| 32 | L3 | x=10 eps=0.01 lo=3.1543 hi=3.16406 width=0.00977 | The interval is narrower than eps, so stop. |
| 33 | L11 | x=10 eps=0.01 return=3.15918 | return 3.15918 (Math.sqrt gives 3.16228). Error is under eps after 10 halvings. |
Epsilon stopping
Stopping when hi - lo <= eps guarantees the returned midpoint is within eps / 2 of the true answer. Each iteration halves the width, so it takes about log₂((hi - lo) / eps) steps.
An absolute epsilon breaks down at extreme magnitudes: 1e-9 is too strict for values near 1e12 (doubles cannot even represent that gap) and too loose for values near 1e-12.
| Initial width | eps | Iterations |
|---|---|---|
| 1 | 1e-3 | 10 |
| 1000 | 1e-3 | 20 |
| 1000 | 1e-6 | 30 |
| 1e9 | 1e-6 | 50 |
Predicate precision
The test inside the loop must be monotonic even in floating point. mid * mid < x is safe; comparing doubles for exact equality is not.
When the predicate is expensive or noisy, or precision near the boundary is limited, use a fixed number of iterations instead of an epsilon. One hundred halvings shrink the interval by 2^100 ≈ 10^30, which is more precision than a double holds for the ranges met in practice.
1double lo = 0, hi = Math.max(1, x);2for (int iter = 0; iter < 100; iter++) { // fixed count: always terminates3double mid = (lo + hi) / 2;4if (mid * mid < x) lo = mid;5else hi = mid;6}7return (lo + hi) / 2;
Binary search on real values
Any monotonic real function can be inverted this way. Examples: the smallest time t at which a moving object reaches a distance, the interest rate that hits a target balance, or the cube root of a number.
For continuous optimisation of a unimodal function, the same shrinking-interval idea becomes ternary search, covered in the next lesson.
- Bound the range first. Doubling
hiuntil the predicate flips works when no upper bound is known. - Print with fixed precision (e.g.
String.format("%.6f", ans)) to match judge output.
Practice ladder
Now try it yourself
Easy
- Square root to 6 decimal places
- Cube root of a number, including negatives
Medium
- Find the time when two runners meet
- Minimise the maximum distance to gas stations
Advanced
- Maximum average subarray of length >= k (binary search on the average)
- Solve f(x) = 0 for a monotonic f given as a black box
Key takeaways
- Bisection halves a real interval each step; precision costs about log₂(width / eps) iterations.
- Prefer a fixed iteration count when floating-point limits make an epsilon unreliable.
- The predicate must stay monotonic under floating-point arithmetic.