Exponential approximation not good for small or large input

606 views Asked by At

Exponential approximation code based on tailor series https://en.wikipedia.org/wiki/Taylor_series works well for inputs around zero but is completely useless when moving further away in either direction. Below is the output of my little test code that calculates exp for inputs within -12 to 12 range and prints error compared to the std::exp results and the errors at the borders are huge. For example error for -12 input is about whopping 148255571469%:

in = -12 error = 148255571469.28%
in = -11.00 error = 18328703925.31%
in = -10.00 error = 2037562880.10%
in = -9.00 error = 199120705.27%
in = -8.00 error = 16588916.06%
in = -7.00 error = 01128519.76%
in = -6.00 error = 00058853.00%
in = -5.00 error = 00002133.29%
in = -4.00 error = 00000045.61%
in = -3.00 error = 00000000.42%
in = -2.00 error = 00000000.00%
in = -1.00 error = 00000000.00%
in = 0.00 error = 00000000.00%
in = 1.00 error = 00000000.00%
in = 2.00 error = 00000000.00%
in = 3.00 error = 00000000.00%
in = 4.00 error = 00000000.03%
in = 5.00 error = 00000000.20%
in = 6.00 error = 00000000.88%
in = 7.00 error = 00000002.70%
in = 8.00 error = 00000006.38%
in = 9.00 error = 00000012.42%
in = 10.00 error = 00000020.84%
in = 11.00 error = 00000031.13%
in = 12.00 error = 00000042.40%

I need to approximate with an error less that 1% error across the range as big as possible. Any ideas how to achieve this?

My little test code is below:

    #include <cmath>
    #include <iostream>
    #include <iomanip>

    double my_exp(double x) //based on https://en.wikipedia.org/wiki/Exponential_function
    {
        double res = 1. + x, t = x;
        unsigned long factorial = 1;
        for (unsigned char i = 2; i <= 12; ++i)
        {
           t *= x, factorial *= i;
           res += t / factorial;
        }
        return res;
    }

    int main(int argc, char* argv[])
    {
        for (double in = -12; in <= 12; in += 1.)
        {
            auto error = std::abs(my_exp(in) - std::exp(in));
            auto percent = error * 100. / std::exp(in);
            std::cout << "in = " << in << " error = "
              << std::fixed << std::setw( 11 ) << std::setprecision( 2 )
              << std::setfill( '0' ) << percent << "%" << std::endl;
        }
        return 0;
    }

Solutions from seemingly similar question from "Approximate e^x" Approximate e^x does not solve this problem:

  • solutions based on Remez and Pade approximation only provide accuracy within limited range (https://stackoverflow.com/a/6985347/5750612)
  • e^x = 2 x/ln(2) comes down toapproximation of pow and I could not find a precise one
  • tailor series does not work for small and big inputs
  • expf_fast solution produces more uniform error across all range but it is still too big (~20% at the end of range)
1

There are 1 answers

1
kakamelo On

The one of the simplest way to find an approximation with the Tailor expansion is checking a convergence rate just adding a new higher term and confirming a result.

Here is the my c++ code to support the above ideas.

#include <iostream>
#include <cmath>

double my_exp(const double x, const double x0 = 0, const double ncut = 1e-3)
{
    double res = 1.; 
    double addterm = (x - x0);
    size_t norder = 1; 

    while(true)
    {
        double res_update = res + addterm;
        if(std::abs(res_update - res)/std::abs(res) < ncut){
            break;
        }
        norder += 1;
        addterm *= (x - x0)/norder;
        res = res_update;
    }

    return res;
}


int main(int argc, char* argv[])
{
    const double x = std::atof(argv[1]);
    const double approxi = my_exp(x);

    const double exactResult = std::exp(x);

    std::cout<<"approxi : "<< approxi<<std::endl;
    std::cout<<"exact : "<< exactResult<<std::endl;

    std::cout<<"err: "<< (1 - std::abs(approxi/exactResult))*100 <<std::endl;

    return 0;
}