public static BigDecimal Exp(BigDecimal x) { /* To calculate the value if x is negative, use exp(-x) = 1/exp(x) */ if (x.CompareTo(BigDecimal.Zero) < 0) { BigDecimal invx = Exp(x.Negate()); /* Relative error in inverse of invx is the same as the relative errror in invx. * This is used to define the precision of the result. */ var mc = new MathContext(invx.Precision); return BigDecimal.One.Divide(invx, mc); } if (x.CompareTo(BigDecimal.Zero) == 0) { /* recover the valid number of digits from x.ulp(), if x hits the * zero. The x.precision() is 1 then, and does not provide this information. */ return ScalePrecision(BigDecimal.One, -(int) (System.Math.Log10(x.Ulp().ToDouble()))); } /* Push the number in the Taylor expansion down to a small * value where TAYLOR_NTERM terms will do. If x<1, the n-th term is of the order * x^n/n!, and equal to both the absolute and relative error of the result * since the result is close to 1. The x.ulp() sets the relative and absolute error * of the result, as estimated from the first Taylor term. * We want x^TAYLOR_NTERM/TAYLOR_NTERM! < x.ulp, which is guaranteed if * x^TAYLOR_NTERM < TAYLOR_NTERM*(TAYLOR_NTERM-1)*...*x.ulp. */ double xDbl = x.ToDouble(); double xUlpDbl = x.Ulp().ToDouble(); if (System.Math.Pow(xDbl, TaylorNterm) < TaylorNterm*(TaylorNterm - 1.0)*(TaylorNterm - 2.0)*xUlpDbl) { /* Add TAYLOR_NTERM terms of the Taylor expansion (Euler's sum formula) */ BigDecimal resul = BigDecimal.One; /* x^i */ BigDecimal xpowi = BigDecimal.One; /* i factorial */ BigInteger ifac = BigInteger.One; /* TAYLOR_NTERM terms to be added means we move x.ulp() to the right * for each power of 10 in TAYLOR_NTERM, so the addition won't add noise beyond * what's already in x. */ var mcTay = new MathContext(ErrorToPrecision(1d, xUlpDbl/TaylorNterm)); for (int i = 1; i <= TaylorNterm; i++) { ifac = ifac.Multiply(BigInteger.ValueOf(i)); xpowi = xpowi.Multiply(x); BigDecimal c = xpowi.Divide(new BigDecimal(ifac), mcTay); resul = resul.Add(c); if (System.Math.Abs(xpowi.ToDouble()) < i && System.Math.Abs(c.ToDouble()) < 0.5*xUlpDbl) break; } /* exp(x+deltax) = exp(x)(1+deltax) if deltax is <<1. So the relative error * in the result equals the absolute error in the argument. */ var mc = new MathContext(ErrorToPrecision(xUlpDbl/2d)); return resul.Round(mc); } else { /* Compute exp(x) = (exp(0.1*x))^10. Division by 10 does not lead * to loss of accuracy. */ var exSc = (int) (1.0 - System.Math.Log10(TaylorNterm*(TaylorNterm - 1.0)*(TaylorNterm - 2.0)*xUlpDbl /System.Math.Pow(xDbl, TaylorNterm))/(TaylorNterm - 1.0)); BigDecimal xby10 = x.ScaleByPowerOfTen(-exSc); BigDecimal expxby10 = Exp(xby10); /* Final powering by 10 means that the relative error of the result * is 10 times the relative error of the base (First order binomial expansion). * This looses one digit. */ var mc = new MathContext(expxby10.Precision - exSc); /* Rescaling the powers of 10 is done in chunks of a maximum of 8 to avoid an invalid operation * response by the BigDecimal.pow library or integer overflow. */ while (exSc > 0) { int exsub = System.Math.Min(8, exSc); exSc -= exsub; var mctmp = new MathContext(expxby10.Precision - exsub + 2); int pex = 1; while (exsub-- > 0) pex *= 10; expxby10 = expxby10.Pow(pex, mctmp); } return expxby10.Round(mc); } }