cmath

Fast Exponential Implementation in C - based on 2nd degree polynomial


I made a fast approximation of the exponential function (e^x) based on a 2nd-degree polynomial. However, the program doesn't give reasonable numbers if x<0.0 .

The code is like this:

// Compile with: gcc -std=c99 fastexp.c -o fastexp

#include <stdio.h>
#include <math.h>

#define ALPHA 0.50617024f
#define ALPHAc 0.49382976f
#define INV_LN2 1.4426950408889634f

float fast2pxm1_m(float x) { // 0.0 <= x <= 1.0
  return x*(ALPHA+ALPHAc*x);
}

float fast2px(float x) {
  float m, mp;
  float e;

  m = modff(x, &e);
  mp = fast2pxm1_m(m) + 1.0f;
  
  return ldexpf(mp, e);
}

float fastexp(float x) {  // exportable
  return fast2px(x*INV_LN2);
}

int main(int argc, char *argv[]) {
  float x;

  for (int i = -10; i <= 10; i++) {
     x= (float) i/10.0*2;
     printf("%.8f => %.8f\n",x,fastexp(x));
  }
  return 0;
}

The results of the function fastexp are not good for x<0.0: its evolution along x is erroneous, with local minima in several places (x). See the results of the program:

-2.00000000 => 0.23474069
-1.79999995 => 0.21845233
-1.60000002 => 0.22272082
-1.39999998 => 0.24754614
-1.20000005 => 0.44696173
-1.00000000 => 0.43635058
-0.80000001 => 0.46685311
-0.60000002 => 0.93187356
-0.40000001 => 0.87235498
-0.20000000 => 0.89506382
0.00000000 => 1.00000000
0.20000000 => 1.18716359
0.40000001 => 1.45655441
0.60000002 => 1.80817270
0.80000001 => 2.17952919
1.00000000 => 2.64171839
1.20000005 => 3.26836252
1.39999998 => 4.04080629
1.60000002 => 4.81200027
1.79999995 => 5.91210270
2.00000000 => 7.34111547

EDIT:

The constants below give a better approximation:

#define ALPHA 0.6563658616549711f               
#define ALPHAc 0.34363413834502887f             

Solution

  • The polynomial you're using in fast2pxm1_m approximates the function pow(2, x) - 1, but it does this well only in the range [0, 1], as evidenced by the comment // 0.0 <= x <= 1.0.

    The value you're passing to that function is the return value from modff which has the same sign as the input (and therefore is in the range (-1, 1)), so the result is inaccurate for negative inputs.

    One way to fix this is to modify the output of modff so that the fractional part is always non-negative:

    m = modff(x, &e);
    if (m < 0.0f) {
        m += 1.0f; // fractional part
        e -= 1.0f; // integral part
    }
    

    This gives more reasonable outputs:

         x         fastexp(x)       exp(x)
    ----------------------------------------
    -2.00000000 => 0.13306235     0.13533528
    -1.79999995 => 0.16054048     0.16529890
    -1.60000002 => 0.19829696     0.20189651
    -1.39999998 => 0.24633193     0.24659697
    -1.20000005 => 0.29292828     0.30119420
    -1.00000000 => 0.35886729     0.36787944
    -0.80000001 => 0.44536310     0.44932896
    -0.60000002 => 0.53846931     0.54881162
    -0.40000001 => 0.65119916     0.67032004
    -0.20000000 => 0.80504274     0.81873075
     0.00000000 => 1.00000000     1.00000000
     0.20000000 => 1.18716359     1.22140276
     0.40000001 => 1.45655441     1.49182471
     0.60000002 => 1.80817270     1.82211884
     0.80000001 => 2.17952919     2.22554096
     1.00000000 => 2.64171839     2.71828183
     1.20000005 => 3.26836252     3.32011708
     1.39999998 => 4.04080629     4.05519987
     1.60000002 => 4.81200027     4.95303254
     1.79999995 => 5.91210270     6.04964718
     2.00000000 => 7.34111547     7.38905610