Beispiel #1
0
        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);
            }
        }