I recently needed to create a function to approximate a complex trigonometric function on an embedded system without a floating point unit and without a fast trigonometric library. So I pulled out my ancient copy of "Numerical Recipes in C, Second Edition" by Press, Teukolsky, Vetterling and Flannery, and updated the Chebyshev polynomial approximation functions to use modern C++.
For an approximation of order \$N\$ within the range \$[-1,1]\$, the code calculates the Chebyshev approximation coefficients of the passed function \$f\$ as: $$ c_j = \frac{2}{N}\sum_{k=1}^{N} f\left[ \cos\left(\frac{\pi(k - \frac{1}{2})}{N}\right)\right] \cos\left(\frac{\pi j (k - \frac{1}{2})}{N}\right) $$
To make the code more general, we map any arbitrary input range \$[a, b]\$ to \$[-1, 1]\$ using $$ x' = \frac{x - \frac{1}{2}(b + a)}{\frac{1}{2}(b - a)} $$
This code then uses Clenshaw's algorithm to evaluate the approximation.
The demo function takes a function to be approximated, low and high limits and the order of the approximation as inputs and calculates the coefficients and prints 100 values and the error value of the approximation at each of those points, and the maximum of the absolute error. It then exercises the checked evaluation of the approximation (which does range checking) to provoke a thrown std::range_error. The checked evaluation uses an unchecked evaluation to actually perform the Clenshaw algorithm and evaluate the approximation.
A plot of the error values for a 7th order approximation of the cosine function in the range \$[0, \frac{\pi}{2}]\$ is shown below. It has the characteristic undulating error value of a Chebyshev approximation.

#include <cmath>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <limits>
#include <numbers>
#include <stdexcept>
#include <vector>
template <typename T>
class ChebApprox {
public:
// given a function implementation, create a Chebyshev polynomial approximation
ChebApprox(T lowerbound, T upperbound, unsigned n, T (*func)(T));
// range-check evaluation of Chebyshev approximation
T operator()(T x) const;
// unchecked evaluation of Chebyshev approximation
T chebeval(T x) const;
// returns beginning iterator of coefficients list
auto begin() const { return coefficients.cbegin(); }
// returns ending iterator of coefficients list
auto end() const { return coefficients.cend(); }
private:
T lowerbound;
T upperbound;
std::vector<T> coefficients;
};
template <typename T>
ChebApprox<T>::ChebApprox(T lowerbound, T upperbound, unsigned n, T (*func)(T))
: lowerbound{lowerbound}
, upperbound{upperbound}
{
const auto bma{(upperbound - lowerbound)/2};
const auto bpa{(upperbound + lowerbound)/2};
std::vector<T> f;
f.reserve(n);
coefficients.reserve(n);
for (unsigned k = 0; k < n; ++k) {
T y{std::cos(std::numbers::pi * (k + 0.5) / n)};
f.push_back((*func)(y * bma + bpa));
}
const T fac = 2.0/n;
for (unsigned j=0; j < n; ++j) {
T sum{0};
for (unsigned k=0; k < n; ++k) {
sum += f[k] * std::cos(std::numbers::pi * j * (k+0.5) / n);
}
coefficients.push_back(fac*sum);
}
}
template <typename T>
T ChebApprox<T>::operator()(T x) const {
if (x < lowerbound || x > upperbound) {
throw std::range_error("Approximation function input out of allowed range");
}
return chebeval(x);
}
template <typename T>
T ChebApprox<T>::chebeval(T x) const {
T y{(2 * x - lowerbound - upperbound) / (upperbound - lowerbound)};
T y2{2 * y};
T d{0};
T dd{0};
for (auto j{coefficients.size()-1}; j; --j) {
auto sv{d};
d = y2 * d - dd + coefficients[j];
dd = sv;
}
return y * d - dd + coefficients[0] / 2;
}
void demo(double (*func)(double), double lo, double hi, unsigned n)
{
ChebApprox<double> ch{lo, hi, n, func};
std::cout << "Coefficients:\n" << std::setprecision(std::numeric_limits<double>::digits10 + 1);
std::copy(ch.begin(), ch.end(), std::ostream_iterator<double>(std::cout, ", "));
std::cout << "\nEvaluation\n";
auto delta{(hi-lo)/100};
double maxerror{0};
for (auto x{lo}; x < hi; x += delta) {
auto calc{ch(x)};
auto error{calc - func(x)};
if (std::fabs(error) > std::fabs(maxerror)) { maxerror = error; }
std::cout << x << '\t' << calc << '\t' << error << '\n';
}
std::cout << "Maximum absolute error was " << maxerror << '\n';
try {
auto bad{ch(hi + 1)}; // outside defined range
} catch (std::range_error &err) {
std::cout << err.what() << '\n';
}
}
int main() {
demo(std::cos, 0, std::numbers::pi/2, 7);
demo(std::sqrt, 0, 100, 5);
}