Solving and computing numerically. For contest purposes, binary search with a fixed iteration count is often the right root finder (slower per step, unconditionally correct); Newton is the tool when speed or precision doubling matters, and it reappears everywhere as a doubling scheme.
| Method | Convergence | Needs | Note |
|---|---|---|---|
| bisection | linear, 1 bit per step | no | always works given a sign change |
| Newton | quadratic near a simple root | yes | correct digits double each step |
| secant | superlinear, order | no | finite difference instead of |
| regula falsi | linear to superlinear | no | |
| Brent / safeguarded Newton | superlinear, guaranteed | no / yes | hybrid with a bracket |
| fixed-point iteration | linear if | no |
Newton’s method
| Failure | Symptom | Fix |
|---|---|---|
| huge jump, divergence | bisection fallback | |
| bad starting point | another root, or divergence | bracket first |
| multiple root ( at the root) | only linear convergence | for multiplicity |
| oscillation | cycles between two points | damping or a hybrid |
Optimization via Newton: applying it to , , finds a stationary point quadratically, but any critical point, not necessarily a minimum; ternary search is safer for unimodal functions in contests.
Newton beyond real numbers. The same iteration works wherever there is a “derivative”, and each step doubles the precision:
| Setting | Iteration | Doubles |
|---|---|---|
| reals | correct digits | |
| power series inverse | correct coefficients: Power Series | |
| power series , , | , | coefficients |
| -adic (Hensel lifting) | the power of : Modular Square Roots | |
| matrix inverse (Newton–Schulz) | correct digits | |
| big-integer reciprocal | bits: how big-number division is implemented |
Because the last doubling step dominates, the total cost is , the cost of one multiplication: this turns “invert a power series in ” into a solved problem.
template<class F, class D> double newton(F f, D df, double x, int iters = 60) {
for (int i = 0; i < iters; i++) {
double d = df(x); if (d == 0) break;
double nx = x - f(x) / d;
if (fabs(nx - x) < 1e-15 * max(1.0, fabs(x))) return nx;
x = nx;
}
return x;
}
template<class F, class D> double safeNewton(F f, D df, double lo, double hi) { // f(lo), f(hi) of opposite signs
double x = (lo + hi) / 2; bool negLo = f(lo) < 0;
for (int i = 0; i < 200; i++) {
double fx = f(x); if (fx == 0) return x;
if ((fx < 0) == negLo) lo = x; else hi = x;
double d = df(x), nx = d != 0 ? x - fx / d : (lo + hi) / 2;
x = (nx > lo && nx < hi) ? nx : (lo + hi) / 2; // fall back to bisection
}
return x;
}
ll isqrtFloor(ll n) { // floor(sqrt(n)), n >= 0: correct the double guess
ll x = (ll)sqrtl((long double)n);
while (x > 0 && (i128)x * x > n) x--;
while ((i128)(x + 1) * (x + 1) <= n) x++;
return x;
}
ull isqrtNewton(ull n) { // floor(sqrt(n)) with integer Newton steps
if (n < 2) return n;
ull x = n, y = x / 2 + (x & 1); // (x + n / x) / 2 for x = n, without overflow
while (y < x) x = y, y = (x + n / x) / 2;
return x;
}
i128 ipowCap(ll b, int k, i128 cap) { i128 r = 1; while (k--) { r *= b; if (r > cap) return cap + 1; } return r; }
ll iroot(ll n, int k) { // floor(n^(1/k)), n >= 0, k >= 1
ll x = (ll)powl((long double)n, 1.0L / k);
while (x > 0 && ipowCap(x, k, n) > n) x--;
while (ipowCap(x + 1, k, n) <= n) x++;
return x;
}Always correct the result of sqrt / pow: on a double, sqrt(n) for near is often off by one, a classic wrong answer; the adjustment loops cost nothing.
Proof: quadratic convergence, the safeguard, and the integer roots
Quadratic. Let be a simple root and . Taylor: , so ; near the factor is bounded, so . At a root of multiplicity , behaves like , so the plain step only removes a factor of the error, and restores quadratic convergence.
Safeguarded Newton. The bracket always contains a sign change and never grows; every iteration either takes a Newton step inside the bracket or bisects, and the bracket is updated with the sign at the current point, so it shrinks to the root.
Integer square root.
isqrtFloorends with by its two loops (computed in__int128), whatever the initial guess. InisqrtNewton, for the next value satisfies (AM–GM on and ; floors only lose fractional parts) and (since ); so the sequence strictly decreases while above and never drops below it, and at we have , so and the loop stops. The first step from computes as to avoid overflow.irootis the same correction with powers capped at to avoid overflow. All checked against exact__int128arithmetic near perfect powers.
Polynomial roots
All distinct real roots of a polynomial, by recursing on the derivative and bisecting monotone pieces. polyRoots(p) searches (Cauchy’s bound); polyRoots(p, lo, hi) only . Cost for degree : derivative levels, up to pieces each, 100 bisection steps of .
double evalPoly(const vector<double>& p, double x) { double r = 0; for (int i = sz(p) - 1; i >= 0; i--) r = r * x + p[i]; return r; }
vector<double> polyRoots(vector<double> p, double lo, double hi) { // p[i] = coefficient of x^i
while (sz(p) > 1 && p.back() == 0) p.pop_back();
int d = sz(p) - 1;
if (d <= 0) return {};
if (d == 1) { double r = -p[0] / p[1]; if (lo <= r && r <= hi) return {r}; return {}; }
vector<double> der(d);
for (int i = 1; i <= d; i++) der[i - 1] = p[i] * i;
vector<double> pts = polyRoots(der, lo, hi); // critical points split [lo, hi] into monotone pieces
pts.insert(pts.begin(), lo); pts.push_back(hi);
auto nearZero = [&](double x) { // |p(x)| within Horner's rounding error bound
double scale = 0, pw = 1;
for (double c : p) scale += fabs(c) * pw, pw *= max(1.0, fabs(x));
return fabs(evalPoly(p, x)) <= 4 * d * DBL_EPSILON * scale;
};
vector<double> roots;
for (int i = 0; i < sz(pts); i++) {
if (nearZero(pts[i])) { roots.push_back(pts[i]); continue; } // even-multiplicity roots touch zero here
if (i + 1 == sz(pts) || nearZero(pts[i + 1])) continue;
double l = pts[i], h = pts[i + 1]; bool negLeft = evalPoly(p, l) < 0;
if (negLeft == (evalPoly(p, h) < 0)) continue;
for (int it = 0; it < 100; it++) { double mid = (l + h) / 2; ((evalPoly(p, mid) < 0) == negLeft ? l : h) = mid; }
roots.push_back((l + h) / 2);
}
sort(all(roots));
vector<double> res;
for (double r : roots) if (res.empty() || r - res.back() > 1e-9 * max(1.0, fabs(r))) res.push_back(r);
return res;
}
vector<double> polyRoots(vector<double> p) {
while (sz(p) > 1 && p.back() == 0) p.pop_back();
double bound = 1;
for (int i = 0; i + 1 < sz(p); i++) bound = max(bound, 1 + fabs(p[i] / p.back()));
return polyRoots(p, -bound, bound);
}Roots of even multiplicity do not change sign: a critical point with (Horner’s error bound) is reported as a root, and roots closer than (relative) are merged. So a multiple root is reported once, as accurately as a simple root of the derivative; two distinct roots closer than about may merge, and a local extremum within the error bound of 0 is reported as a root. Accuracy depends on conditioning: in tests (degree , roots in at least apart) the error stayed below . Use long double or rescale for clustered roots, high degree or huge coefficients, and strip leading coefficients that are tiny only because of rounding.
Proof: why the derivative recursion finds every root
Between two consecutive real roots of (or the interval ends), has no root, so is strictly monotone there and has at most one root, which exists iff changes sign between the endpoints; bisection then converges to it (the invariant “sign at = sign at the left end, opposite sign at ” is kept). A root not inside any such piece is a critical point, i.e. a multiple root, which the
nearZerotest catches. Cauchy’s bound: if then , so no root lies outside. Tested on random polynomials built from known roots, including double roots.
Integration
| Method | Error | Exact for | Note |
|---|---|---|---|
| rectangle (midpoint) | linear | simplest | |
| trapezoid | linear | ||
| Simpson | cubics | the default | |
| Simpson 3/8 | cubics | ||
| Romberg | high degree | trapezoid + repeated Richardson extrapolation | |
| Gauss–Legendre ( points) | exact to degree | best per evaluation, needs tabulated nodes | |
| Monte Carlo | dimension-free: the only option in high dimensions |
template<class F> double simpson(F f, double a, double b, int n) { // composite, n even
double h = (b - a) / n, s = f(a) + f(b);
for (int i = 1; i < n; i++) s += f(a + i * h) * (i % 2 ? 4 : 2);
return s * h / 3;
}
template<class F> double adaptiveSimpson(F f, double a, double b, double eps, double whole, double fa, double fm, double fb, int depth) {
double c = (a + b) / 2, l = (a + c) / 2, r = (c + b) / 2, fl = f(l), fr = f(r);
double left = (c - a) / 6 * (fa + 4 * fl + fm), right = (b - c) / 6 * (fm + 4 * fr + fb);
if (depth <= 0 || fabs(left + right - whole) <= 15 * eps) return left + right + (left + right - whole) / 15; // Richardson
return adaptiveSimpson(f, a, c, eps / 2, left, fa, fl, fm, depth - 1) + adaptiveSimpson(f, c, b, eps / 2, right, fm, fr, fb, depth - 1);
}
template<class F> double integrate(F f, double a, double b, double eps = 1e-10) {
double fa = f(a), fm = f((a + b) / 2), fb = f(b);
return adaptiveSimpson(f, a, b, eps, (b - a) / 6 * (fa + 4 * fm + fb), fa, fm, fb, 50);
}Adaptive Simpson spends effort where the function is difficult and almost none where it is flat; reusing the already computed values halves the evaluations, and the /15 term is Richardson extrapolation, which upgrades the accuracy for free.
| Integration in contests | Integrand |
|---|---|
| area under a curve, between curves | , the difference |
| arc length | |
| volume of revolution | |
| volume by cross-sections (the most common) | area of the 2D slice at , often itself a geometric computation |
| expected value, probability of a region | , the density |
| union of circles / shapes | slice length or area as a function of |
Simpson assumes smoothness
Adaptive Simpson silently gives wrong answers on functions with discontinuities or kinks: the error estimate compares two smooth approximations that happen to agree. Split the interval at every known breakpoint (where shapes start or stop overlapping, where the maximum changes which curve is on top) and integrate each smooth piece. Also: remove endpoint singularities by substitution, and give oscillatory integrands a finer base subdivision (aliasing).
Exact alternatives, check first: polygon area by the shoelace formula; circle–polygon intersection by closed-form sectors and triangles; piecewise-linear functions as sums of trapezoids; polynomial integrands symbolically. Reach for numerical integration only when there is no closed form. Monte Carlo: with error independent of the dimension (4 times the samples per extra digit); variance reduction (importance sampling, stratification, quasi-random sequences) helps a lot.
Proof: Simpson is exact for cubics, and the Richardson correction
On , Simpson gives . For this is , equal to the exact integrals; by linearity it is exact for all cubics. For it gives instead of , an error of ; with Taylor’s theorem the error on one panel of width is , so panels give overall. Halving the panel width divides the error by about : if is the coarse and the two-half estimate, , which is both the stopping test and the correction added to the result (this makes the answer exact for quintics on each panel). Checked: composite Simpson is exact on random cubics, and
integratematches closed forms (, , , ) to .
Newton–Schulz: matrix inverse by multiplications
The scalar analogue is Newton on : , convergent from any start with . Per iteration: two matrix multiplications, (or ); iterations: to enter the convergence basin plus . Sequentially worse than Gauss–Jordan; it wins where multiplication is much cheaper than elimination: GPUs and distributed systems (embarrassingly parallel GEMMs versus sequential pivoting), settings without division (fixed-point hardware, homomorphic encryption, some rings), and refinement (one step squares the error of an approximate inverse from any source). The same iteration converges to the Moore–Penrose pseudo-inverse for rectangular , and relatives compute the matrix sign function and the polar decomposition.
vector<vector<double>> newtonSchulzInverse(const vector<vector<double>>& A, int iters = 100) {
int n = sz(A); double n1 = 0, ninf = 0;
for (int j = 0; j < n; j++) { double s = 0; for (int i = 0; i < n; i++) s += fabs(A[i][j]); n1 = max(n1, s); }
for (int i = 0; i < n; i++) { double s = 0; for (int j = 0; j < n; j++) s += fabs(A[i][j]); ninf = max(ninf, s); }
auto mul = [&](const vector<vector<double>>& P, const vector<vector<double>>& Q) {
vector<vector<double>> R(n, vector<double>(n, 0));
for (int i = 0; i < n; i++) for (int k = 0; k < n; k++) for (int j = 0; j < n; j++) R[i][j] += P[i][k] * Q[k][j];
return R;
};
vector<vector<double>> X(n, vector<double>(n));
for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) X[i][j] = A[j][i] / (n1 * ninf);
for (int it = 0; it < iters; it++) {
auto AX = mul(A, X); double err = 0;
for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) { AX[i][j] = (i == j ? 2 : 0) - AX[i][j]; err += fabs(AX[i][j] - (i == j)); }
X = mul(X, AX);
if (err < 1e-13 * n) break; // AX was already the identity
}
return X;
}Proof: the residual squares, and the starting point converges
With : , so and correct digits double once . For the start, with . is symmetric positive definite (for invertible ) with largest eigenvalue , so the eigenvalues of lie in and its spectral norm is below 1. The number of steps to bring down to is about . Checked against Gaussian elimination on random well-conditioned matrices up to .
Related notes
- Floating Point
- Power Series (Newton on series)
- Binary Search