Bisection and Convergence Rate
Learning Objectives
- State the bracketing guarantee of bisection with its hypotheses, and say what it does not promise.
- Compute the exact iteration count for a tolerance, distinguish iterations from function evaluations, and check the count against
scipy.optimize.bisect. - Measure bisection's convergence, and explain why the bracket width halves exactly while the error does not.
- Choose among width, relative width, residual and iteration-cap stopping tests, and say what each one certifies.
- Predict the attainable accuracy from conditioning, and recognize where rounding ends convergence.
Context & Motivation
guess-and-check-and-bisection-search builds the loop, and continuity-and-the-intermediate-value-theorem explains why a root is inside the bracket, shows a sign change at a pole, and traces how doubles run out. Neither answers the questions an engineer asks of a numerical method. How many iterations does a tolerance cost, exactly? What does the stopping test actually certify? How accurate can the answer be, given the conditioning from conditioning-and-numerical-stability?
The running example is the internal rate of return (IRR), the function behind the spreadsheet of the same name. Cash flows at years have net present value , and the IRR is a root of it. In general there is no closed form. Bisection is slow, but it asks only for a sign change, which is why solvers such as Brent's keep it as a safety net.
Core Theory
The method and its guarantee
Hypotheses. is continuous on , and and are nonzero with opposite signs.
Method. Set . For let be the midpoint of . If , stop. Otherwise is the half whose endpoints have opposite signs.
Conclusion. The Intermediate Value Theorem places a root in every bracket. The nested brackets shrink to one point , and continuity forces . For every ,
Each iteration costs one evaluation of and needs no derivative. The guarantee does not say the root is unique, nor which root is found.
Counting iterations
The bracket width reaches a tolerance at the smallest with :
This count is unusual among numerical methods: it does not depend on , except that an exact hit ends the run early. Function evaluations number , because the two endpoints are checked first. Each decimal digit costs iterations.
Linear convergence, stated carefully
A sequence with errors converges linearly with rate when tends to . The bound satisfies exactly. The true error in general does not, because a midpoint can land close to by luck and the next one farther away. So rate describes the bound that dominates the error, one binary digit per iteration on average, and not a ratio observed at each step. secant-method-and-order-of-convergence makes order of convergence formal.
Stopping criteria
| Test | Certifies | Weakness |
|---|---|---|
| Width: | within , if the signs are right | Means little if is much smaller than ; may never end if is below the float spacing at |
| Relative width: | Digits of | Cannot be met when |
| Residual: | is an exact root of | Depends on the units of |
| Iteration cap | The loop ends | Nothing about accuracy |
Real code combines them. scipy.optimize.bisect takes xtol, rtol and maxiter, with defaults , and , and documents the result as satisfying np.isclose(x, x0, atol=xtol, rtol=rtol). Its default xtol is an absolute test: on over it reports success at , an 82 percent relative error, while xtol=1e-20 gives in 67 iterations.
Residual, width and conditioning
Width bounds the forward error. The residual is the backward error: solves exactly. For a simple root ( with continuous nearby), first-order expansion links them, , so is the absolute condition number of the root.
This also sets the accuracy floor. If computed values of carry noise of size , signs are trustworthy only where , so near a simple root the zone of doubt has half-width about . Inside it the bracket keeps halving around a computed sign change, and its width no longer measures the error.
Worked Examples
Example 1: an IRR to
Take cash flows . is continuous on , with and . Substituting gives a cubic with one sign change in its coefficients, so by Descartes' rule there is exactly one root with : . For , . The run, with the bound beside the true error:
textn m_n f(m_n) e_n 2^-n e_n/e_(n-1) 1 0.5 -4.12e+02 3.37e-01 5.00e-01 2 0.25 -1.43e+02 8.66e-02 2.50e-01 0.26 3 0.125 +7.61e+01 3.84e-02 1.25e-01 0.44 4 0.1875 -4.35e+01 2.41e-02 6.25e-02 0.63 5 0.15625 +1.35e+01 7.16e-03 3.13e-02 0.30 6 0.171875 -1.56e+01 8.47e-03 1.56e-02 1.18 7 0.1640625 -1.23e+00 6.57e-04 7.81e-03 0.08 8 0.16015625 +6.10e+00 3.25e-03 3.91e-03 4.95 ... 26 0.16340561 -2.13e-05 1.14e-08 1.49e-08 0.43 27 0.16340560 -7.42e-06 3.97e-09 7.45e-09 0.35
The bound holds at every row. The error rises in 7 of the 26 transitions, and yet the fitted slope of against is , and the geometric mean of the ratios is . The method spent 29 evaluations. scipy.optimize.bisect(npv, 0, 1, xtol=1e-8) reports 27 iterations and 29 function calls and returns the same . With the same xtol, scipy.optimize.brentq uses 9 calls and lands within of .
Example 2: the residual test depends on units
Express the same cash flows at different scales and stop on , or on width :
textscale of cash flows iterations, residual test error in r iterations, width test 0.001 7 6.57e-04 27 1 16 7.71e-07 27 1000 24 3.48e-09 27 1000000 36 1.15e-12 27
The same rate of return, the same test, and an error that changes by eight orders of magnitude. The width test does not notice the scale. At scale , , and the stopping residual predicts an error of , matching the table. A residual test is right when itself has meaning, as in "the books balance within ", and wrong as a proxy for digits of .
Example 3: where convergence stops
Run the IRR to the end of the floats, stopping when the midpoint equals an endpoint:
textformat halvings final width true error float64 55 2.8e-17 5.4e-17 float32 26 1.5e-08 3.5e-09
The count is 55 and not 53, as it is for the root near in continuity-and-the-intermediate-value-theorem, because near the double spacing is . In general the count is about . In float64 the true error is twice the final width, so lies outside the final bracket. This is the noise floor at work: on the 801 consecutive doubles centered on the computed NPV is off by up to , which over gives a zone of , a few float spacings, and inside it computed signs can be wrong. These results depend on how NPV is written. The table evaluates it term by term, and in float64 Horner's rule in hits an exact 0.0 after 46 iterations instead, with error .
Now take an ill-conditioned root. has , and its expanded form is evaluated by Horner's rule:
textbracket midpoints final width result true error [0.8, 1.1] 7 4.7e-03 0.99453125 5.5e-03 [0.9, 1.4] 15 3.1e-05 0.99248352 7.5e-03 [0.7, 1.2] 21 4.8e-07 0.99500604 5.0e-03 [0.95, 1.02] 47 1.1e-15 1.00734781 7.3e-03
Every run stops because Horner returns exactly 0.0, a rounding coincidence and not a root. That last midpoint ends the run before the bracket is halved, so midpoints leave a width of about . The last row reports a width near with an error more than times larger. On a grid of 600,001 points in the computed sign flips 73,860 times, between and . On the 100,000 grid points with the largest computed is , all noise, since the true value there is below . And equals that at , which is the predicted zone. scipy.optimize.bisect and brentq on the last bracket return and , so a better solver does not help. Bisection on the factored form (x - 1)**7 returns exactly on all four brackets: the problem was the representation of .
Example 4: missing hypotheses
Two roots, no sign change. Cash flows have IRRs of 10 and 20 percent, but and . scipy.optimize.bisect raises ValueError on . On and it finds each root. A sign change is sufficient for a root, not necessary, and an even number of roots hides from it.
A pole. scipy.optimize.bisect(math.tan, 1, 2) reports convergence after 39 iterations at , where equals . Continuity fails, as for in continuity-and-the-intermediate-value-theorem, and the width test cannot see that. A check of at the end can.
Common Misconceptions & Pitfalls
- "Linear convergence means the error halves each step." The bound does. In Example 1 the error once grew by a factor of , between and .
- "Testing
f(a) * f(m) < 0is the same as comparing signs." With the product underflows to-0.0, the test is always false, and the loop converges to . Comparing signs directly gives . - "
(a + b) / 2is always safe." It overflows when and are huge with the same sign:1.6e308 + 1.7e308givesinf.a + (b - a) / 2fixes that case and fails on the opposite one: witha = -1.7e308andb = 1.7e308,b - aoverflows and the result isinf, while(a + b) / 2gives0.0. A bracket that straddles zero is the normal case for a root near , so neither spelling is safe for every bracket at this scale.a / 2 + b / 2handles both cases, at the cost of rounding when the endpoints are subnormal. - "Width below tolerance means the answer is that good." Only if the computed signs are right, which Example 3 shows can fail.
- "A relative test handles every root." With only a relative test, on has no exit until the floats run out, after 1076 iterations down through the subnormals to
5e-324. A root at needs an absolute floor. - "Bisection is obsolete." SciPy's documentation rates
brentqgenerally the best of its rootfinding routines, and describes it as combining bracketing, bisection and inverse quadratic interpolation. Bisection survives inside it, and plain bisection is still a method whose cost can be stated before running it.
Summary
For continuous with a sign change on , bisection keeps a root in a bracket of width , using one evaluation per iteration, and needs iterations for width regardless of . The rate belongs to the bound: the true error can rise from one step to the next while averaging one bit per iteration. Width controls forward error, residual controls backward error, and links them near a simple root. Absolute tests fail for tiny roots, relative tests fail at zero, residual tests depend on units, and a cap only ends the loop. Rounding ends convergence at the float spacing for a well-conditioned root, and much earlier for an ill-conditioned one, where bracket width stops measuring error.
Documentation Links
- SciPy documentation: scipy.optimize.bisect
- SciPy documentation: scipy.optimize.brentq
- Driscoll and Braun: Fundamentals of Numerical Computation, 4.1 The rootfinding problem
- MIT OpenCourseWare 18.330 (2012): Lecture Notes: Nonlinear Equations
- Goldberg (1991): What Every Computer Scientist Should Know About Floating-Point Arithmetic