
# expApprox.py
# Andrew Davison, ad@coe.psu.ac.th, June 2026
"""
  Evaluate exp(x) using a Taylor series with argument reduction.
  The input is reduced using the nearest multiple of ln(2):
    x = k*ln(2) + xR
  where:
    k = round(x / ln(2))
  This guarantees:
    -ln(2)/2 <= xR <= ln(2)/2
  so the Taylor series is evaluated only on the small interval,
  which results in a rapid convergence.
  
  The result for the original value is constructed using:
    exp(x) = 2**k * exp(xR)

  The test-rig compares the approximation and math.exp(x)
  over a wide range of values. 
"""

import math


LN2 = math.log(2)
  # 0.693147180559945309417232121458176568


def expTaylor(x, maxIters=16, eps=1e-15):
  if x == 0.0:
    return 1.0
  
  k = int(round(x/LN2))
  xR = x - k*LN2
  term = 1.0
  tot = 1.0
  for n in range(1, maxIters):
    term *= xR/n
    tot += term
    if abs(term) <= eps * max(1.0, abs(tot)):
      # print(n)   # usually about 12
      break
  return tot * (2.0**k)


# -------------------------------------
if __name__ == "__main__":
  print("     x       Approx      math.exp(x)     Error")
  print("-" * 50)
  
  testVals = [ -100, -30, -10, -5, -1, -0.5, 
                0.5, 1, 5, 10, 30, 100 ]
  for x in testVals:
    approx = expTaylor(x)
    exact = math.exp(x)
    relErr = abs(approx - exact)/exact
    print( f"{x:8.1f} " f"{approx:13.5e} " f"{exact:13.5e} " f"{relErr:11.3e}")
  
