Learn how the Newton-Raphson method finds function roots using tangent lines. Includes step-by-step C++ implementation, mathematical derivation, and a coding challenge.
π How Newtonβs Method Finds Roots
Letβs start with a function and determine its roots.
Newtonβs method suggests starting with an initial guess, say 2, as our . Then, we draw a tangent line at this point.
If we directly observe the graphic, we can see that the intersection point, denoted as , where the tangent line crosses the x-axis is very close to our root. But how do we determine this intersection point?
π The Math Behind the Iteration
Letβs consider the definition of a tangent:
Plugging in for , and knowing which is and which becomes :
Now, letβs isolate to express it as a function of , , and :
Therefore, if we provide an initial guess, the function we want to resolve, and its derivative, we can find the root.
π οΈ Building the Algorithm in C++
Letβs implement this step by step.
As discussed, we need a function that takes an initial guess, the function itself, and its derivative. To use std::function, we include the <functional> header file.
#include <functional>
double newton_raphson(std::function<double(double)> func, std::function<double(double)> dervi, double initialGuess)
{
// implementation here
}
We need a tolerance to stop our calculation and a maximum iteration count to prevent looping forever. We use size_t, defined in <cstddef>.
#include <functional>
#include <cstddef>
double newton_raphson(std::function<double(double)> func, std::function<double(double)> dervi,
double initialGuess, double tolerance, size_t maxIteration)
{
// implementation here
}
In this function, we will loop until the maximum iteration is reached or we find a result within the tolerance error. Letβs define a while loop for this.
#include <functional>
#include <cstddef>
double newton_raphson(std::function<double(double)> func, std::function<double(double)> dervi,
double initialGuess, double tolerance, size_t maxIteration)
{
size_t currentIteration = 0;
while (currentIteration <= maxIteration)
{
++currentIteration;
}
}
Before starting the iteration, we should assign our initial guess to the current x, which is the x we are dealing with:
#include...
double newton_raphson(std::function<double(double)> func, std::function<double(double)> dervi,
double initialGuess, double tolerance, size_t maxIteration)
{
size_t currentIteration = 0;
double currentX = initialGuess;
while (currentIteration <= maxIteration)
{
++currentIteration;
}
}
For each iteration, we want to calculate the new x using the formula:
#include...
double newton_raphson(std::function<double(double)> func, std::function<double(double)> dervi,
double initialGuess, double tolerance, size_t maxIteration)
{
size_t currentIteration = 0;
double currentX = initialGuess;
while (currentIteration <= maxIteration)
{
// Store the x value from the previous iteration as previousX in each iteration
double previousX = currentX;
// Calculate the function and derivative values at the current iteration
double functionValue = func(previousX);
double derivativeValue = dervi(previousX);
// Update the value using the Newton-Raphson formula
currentX = previousX - functionValue / derivativeValue;
++currentIteration;
}
}
π‘οΈ Adding Tolerance and Safety Checks
Then, we want to return the current x value if we are satisfied with the precision:
#include...
double newton_raphson(std::function<double(double)> func, std::function<double(double)> dervi,
double initialGuess, double tolerance, size_t maxIteration)
{
size_t currentIteration = 0;
double currentX = initialGuess;
while (currentIteration <= maxIteration)
{
// Store the x value from the previous iteration as previousX in each iteration
double previousX = currentX;
// Calculate the function and derivative values at the current iteration
double functionValue = func(previousX);
double derivativeValue = dervi(previousX);
// Update the value using the Newton-Raphson formula
currentX = previousX - functionValue / derivativeValue;
if (std::abs(currentX - previousX) <= tolerance)
return currentX;
++currentIteration;
}
}
Additionally, we need to handle the potential division by zero problem:
#include...
double newton_raphson(std::function<double(double)> func, std::function<double(double)> dervi,
double initialGuess, double tolerance, size_t maxIteration)
{
size_t currentIteration = 0;
double currentX = initialGuess;
while (currentIteration <= maxIteration)
{
// Store the x value from the previous iteration as previousX in each iteration
double previousX = currentX;
// Calculate the function and derivative values at the current iteration
double functionValue = func(previousX);
double derivativeValue = dervi(previousX);
if (std::abs(derivativeValue) < 1e-12)
return currentX;
// Update the value using the Newton-Raphson formula
currentX = previousX - functionValue / derivativeValue;
if (std::abs(currentX - previousX) <= tolerance)
return currentX;
++currentIteration;
}
}
There you go! Youβve just implemented the function! π₯³
πͺ Try It Yourself: The Secant Method
The Newton-Raphson method requires a derivative function to proceed successfully. Instead of the derivative, you can use a finite difference method:
Consequently, two initial points are needed to calculate the first difference.
Try to implement this function by yourself!