Alex's Anthology of Algorithms Common Code for Contests in Concise C++
Optimization / Numerical Methods

5.4.2 Root Finding (Iteration)

5-Optimization/5.4.2_Root_Finding_(Iteration).cpp

Finds an $x$ for a continuous function $f$ such that $f(x) = 0$ using iterative approximation by an initial guess that is close to the answer. Each step follows the tangent line at the current guess down to its $x$-intercept, that is, $x \leftarrow x - f(x)/f'(x)$. Newton's method requires an explicit definition of the function's derivative while the secant method starts with two initial guesses and approximates the derivative using the secant slope from the previous iteration. With smooth functions and good initial guesses, Newton's method has quadratic convergence, while the secant method has convergence order approximately $1.618$.

  • newton_root(f, fprime, x0, eps = 1e-15, iterations = 100) returns a root $x$ for a function f with derivative fprime using an initial guess x0 which should be relatively close to $x$.
  • secant_root(f, x0, x1, eps = 1e-15, iterations = 100) returns a root $x$ for a function f using two initial guesses x0 and x1 which should be relatively close to $x$.

Implementation

#include <cmath>
#include <stdexcept>

template<typename Fn, typename Deriv>
double newton_root(Fn f, Deriv fprime, double x0, double eps = 1e-15, int iterations = 100) {
  double x = x0, error = eps + 1;
  for (int i = 0; std::isfinite(error) && error > eps && i < iterations; i++) {
    double fx = f(x);
    if (fx == 0) {
      return x;
    }
    double xnew = x - fx / fprime(x);
    error = fabs(xnew - x);
    x = xnew;
  }
  if (!std::isfinite(error) || error > eps) {
    throw std::runtime_error("Newton's method failed to converge.");
  }
  return x;
}

template<typename Fn>
double secant_root(Fn f, double x0, double x1, double eps = 1e-15, int iterations = 100) {
  double xold = x0, fxold = f(x0), x = x1, error = eps + 1;
  for (int i = 0; std::isfinite(error) && error > eps && i < iterations; i++) {
    double fx = f(x);
    if (fx == 0) {
      return x;
    }
    double xnew = x - fx * ((x - xold) / (fx - fxold));
    error = fabs(xnew - x);
    xold = x;
    fxold = fx;
    x = xnew;
  }
  if (!std::isfinite(error) || error > eps) {
    throw std::runtime_error("Secant method failed to converge.");
  }
  return x;
}

Example Usage

#include <cassert>

double f(double x) {
  return x * x - 4 * sin(x);
}

double fprime(double x) {
  return 2 * x - 4 * cos(x);
}

int main() {
  assert(fabs(f(newton_root(f, fprime, 3))) < 1e-10);
  assert(fabs(f(secant_root(f, 3, 2))) < 1e-10);
  auto g = [](double x) { return x * x - 2; };
  auto gprime = [](double x) { return 2 * x; };
  assert(fabs(g(newton_root(g, gprime, 1))) < 1e-10);
  return 0;
}