/* Logarithm. Computes log(x) in double-double precision. This is a natural logarithm (i.e., base e). */ dd_real log(const dd_real &a) { /* Strategy. The Taylor series for log converges much more slowly than that of exp, due to the lack of the factorial term in the denominator. Hence this routine instead tries to determine the root of the function f(x) = exp(x) - a using Newton iteration. The iteration is given by x' = x - f(x)/f'(x) = x - (1 - a * exp(-x)) = x + a * exp(-x) - 1. Only one iteration is needed, since Newton's iteration approximately doubles the number of digits per iteration. */ if (a.is_one()) { return 0.0; } if (a.hi <= 0.0) { dd_real::abort("(dd_real::log): Non-positive argument."); return dd_real::_nan; } dd_real x = std::log(a.hi); /* Initial approximation */ x = x + a * exp(-x) - 1.0; return x; }
/* Exponential. Computes exp(x) in double-double precision. */ dd_real exp(const dd_real &a) { /* Strategy: We first reduce the size of x by noting that exp(kr + m * log(2)) = 2^m * exp(r)^k where m and k are integers. By choosing m appropriately we can make |kr| <= log(2) / 2 = 0.347. Then exp(r) is evaluated using the familiar Taylor series. Reducing the argument substantially speeds up the convergence. */ const double k = 512.0; const double inv_k = 1.0 / k; if (a.x[0] <= -709.0) return 0.0; if (a.x[0] >= 709.0) return dd_real::_inf; if (a.is_zero()) return 1.0; if (a.is_one()) return dd_real::_e; double m = std::floor(a.x[0] / dd_real::_log2.x[0] + 0.5); dd_real r = mul_pwr2(a - dd_real::_log2 * m, inv_k); dd_real s, t, p; p = sqr(r); s = r + mul_pwr2(p, 0.5); p *= r; t = p * dd_real(inv_fact[0][0], inv_fact[0][1]); int i = 0; do { s += t; p *= r; ++i; t = p * dd_real(inv_fact[i][0], inv_fact[i][1]); } while (std::abs(to_double(t)) > inv_k * dd_real::_eps && i < 5); s += t; s = mul_pwr2(s, 2.0) + sqr(s); s = mul_pwr2(s, 2.0) + sqr(s); s = mul_pwr2(s, 2.0) + sqr(s); s = mul_pwr2(s, 2.0) + sqr(s); s = mul_pwr2(s, 2.0) + sqr(s); s = mul_pwr2(s, 2.0) + sqr(s); s = mul_pwr2(s, 2.0) + sqr(s); s = mul_pwr2(s, 2.0) + sqr(s); s = mul_pwr2(s, 2.0) + sqr(s); s += 1.0; return ldexp(s, static_cast<int>(m)); }
/* Exponential. Computes exp(x) in double-double precision. */ dd_real exp(const dd_real &a) { /* Strategy: We first reduce the size of x by noting that exp(kr + m) = exp(m) * exp(r)^k Thus by choosing m to be a multiple of log(2) closest to x, we can make |kr| <= log(2) / 2 = 0.3466. Now we can set k = 64, so that |r| <= 0.000542. Then exp(x) = exp(kr + s log 2) = (2^s) * [exp(r)]^64 Then exp(r) is evaluated using the familiar Taylor series. Reducing the argument substantially speeds up the convergence. */ const int k = 64; if (a.hi <= -709.0) return 0.0; if (a.hi >= 709.0) return dd_real::_inf; if (a.is_zero()) { return 1.0; } if (a.is_one()) { return dd_real::_e; } int z = to_int(nint(a / dd_real::_log2)); dd_real r = (a - dd_real::_log2 * static_cast<double>(z)) / static_cast<double>(k); dd_real s, t, f, p; double m; s = 1.0 + r; p = sqr(r); m = 2.0; f = 2.0; t = p / f; do { s += t; p *= r; m += 1.0; f *= m; t = p / f; } while (std::abs(to_double(t)) > 1.0e-35); s += t; r = pow(s, k); r = mul_pwr2(r, std::ldexp(1.0, z)); return r; }