/* 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; }
/* Computes sin(a) and cos(a) using Taylor series. Assumes |a| <= pi/32. */ static void sincos_taylor(const dd_real &a, dd_real &sin_a, dd_real &cos_a) { const double thresh = 1.0e-35 * std::abs(to_double(a)); dd_real t; /* Term being added. */ dd_real s; /* Current partial sum. */ dd_real x; /* = -sqr(a) */ double m; if (a.is_zero()) { sin_a = 0.0; cos_a = 1.0; return; } x = -sqr(a); s = a; t = a; m = 1.0; do { m += 2.0; t *= x; t /= (m*(m-1.0)); s += t; } while (std::abs(to_double(t)) > thresh); sin_a = s; cos_a = sqrt(1.0 - sqr(s)); }
/* 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; }
/* Computes the n-th power of a double-double number. NOTE: 0^0 causes an error. */ dd_real npwr(const dd_real &a, int n) { if (n == 0) { if (a.is_zero()) { dd_real::abort("(dd_real::npwr): Invalid argument."); return dd_real::_nan; } return 1.0; } dd_real r = a; dd_real s = 1.0; int N = std::abs(n); if (N > 1) { /* Use binary exponentiation */ while (N > 0) { if (N % 2 == 1) { s *= r; } N /= 2; if (N > 0) r = sqr(r); } } else { s = r; } /* Compute the reciprocal if n is negative. */ if (n < 0) return (1.0 / s); return s; }
dd_real sinh(const dd_real &a) { if (a.is_zero()) { return 0.0; } if (abs(a) > 0.05) { dd_real ea = exp(a); return mul_pwr2(ea - inv(ea), 0.5); } /* since a is small, using the above formula gives a lot of cancellation. So use Taylor series. */ dd_real s = a; dd_real t = a; dd_real r = sqr(t); double m = 1.0; double thresh = std::abs((to_double(a)) * dd_real::_eps); do { m += 2.0; t *= r; t /= (m-1) * m; s += t; } while (abs(t) > thresh); return s; }
dd_real cos(const dd_real &a) { if (a.is_zero()) { return 1.0; } /* First reduce modulo 2*pi so that |r| <= pi. */ dd_real r = drem(a, dd_real::_2pi); /* Now reduce by modulo pi/2 and then by pi/16 so that we obtain numbers a, b, and t. */ dd_real t; dd_real sin_t, cos_t; dd_real s, c; int j = to_int(divrem(r, dd_real::_pi2, t)); int abs_j = std::abs(j); int k = to_int(divrem(t, dd_real::_pi16, t)); int abs_k = std::abs(k); if (abs_j > 2) { dd_real::abort("(dd_real::cos): Cannot reduce modulo pi/2."); return dd_real::_nan; } if (abs_k > 4) { dd_real::abort("(dd_real::cos): Cannot reduce modulo pi/16."); return dd_real::_nan; } sincos_taylor(t, sin_t, cos_t); if (abs_k == 0) { s = sin_t; c = cos_t; } else { dd_real u = dd_real::cos_table[abs_k-1]; dd_real v = dd_real::sin_table[abs_k-1]; if (k > 0) { s = u * sin_t + v * cos_t; c = u * cos_t - v * sin_t; } else { s = u * sin_t - v * cos_t; c = u * cos_t + v * sin_t; } } if (abs_j == 0) { r = c; } else if (j == 1) { r = -s; } else if (j == -1) { r = s; } else { r = -c; } return r; }
dd_real cosh(const dd_real &a) { if (a.is_zero()) { return 1.0; } dd_real ea = exp(a); return mul_pwr2(ea + inv(ea), 0.5); }
dd_real tanh(const dd_real &a) { if (a.is_zero()) { return 0.0; } dd_real ea = exp(a); dd_real inv_ea = inv(ea); return (ea - inv_ea) / (ea + inv_ea); }
dd_real atan2(const dd_real &y, const dd_real &x) { /* Strategy: Instead of using Taylor series to compute arctan, we instead use Newton's iteration to solve the equation sin(z) = y/r or cos(z) = x/r where r = sqrt(x^2 + y^2). The iteration is given by z' = z + (y - sin(z)) / cos(z) (for equation 1) z' = z - (x - cos(z)) / sin(z) (for equation 2) Here, x and y are normalized so that x^2 + y^2 = 1. If |x| > |y|, then first iteration is used since the denominator is larger. Otherwise, the second is used. */ if (x.is_zero()) { if (y.is_zero()) { /* Both x and y is zero. */ dd_real::abort("(dd_real::atan2): Both arguments zero."); return dd_real::_nan; } return (y.is_positive()) ? dd_real::_pi2 : -dd_real::_pi2; } else if (y.is_zero()) { return (x.is_positive()) ? dd_real(0.0) : dd_real::_pi; } if (x == y) { return (y.is_positive()) ? dd_real::_pi4 : -dd_real::_3pi4; } if (x == -y) { return (y.is_positive()) ? dd_real::_3pi4 : -dd_real::_pi4; } dd_real r = sqrt(sqr(x) + sqr(y)); dd_real xx = x / r; dd_real yy = y / r; /* Compute double precision approximation to atan. */ dd_real z = std::atan2(to_double(y), to_double(x)); dd_real sin_z, cos_z; if (xx > yy) { /* Use Newton iteration 1. z' = z + (y - sin(z)) / cos(z) */ sincos(z, sin_z, cos_z); z += (yy - sin_z) / cos_z; } else { /* Use Newton iteration 2. z' = z - (x - cos(z)) / sin(z) */ sincos(z, sin_z, cos_z); z -= (xx - cos_z) / sin_z; } return z; }
/* Computes the n-th root of the double-double number a. NOTE: n must be a positive integer. NOTE: If n is even, then a must not be negative. */ dd_real nroot(const dd_real &a, int n) { /* Strategy: Use Newton iteration for the function f(x) = x^(-n) - a to find its root a^{-1/n}. The iteration is thus x' = x + x * (1 - a * x^n) / n which converges quadratically. We can then find a^{1/n} by taking the reciprocal. */ if (n <= 0) { dd_real::abort("(dd_real::nroot): N must be positive."); return dd_real::_nan; } if (n%2 == 0 && a.is_negative()) { dd_real::abort("(dd_real::nroot): Negative argument."); return dd_real::_nan; } if (n == 1) { return a; } if (n == 2) { return sqrt(a); } if (a.is_zero()) return 0.0; /* Note a^{-1/n} = exp(-log(a)/n) */ dd_real r = abs(a); dd_real x = std::exp(-std::log(r.hi) / n); /* Perform Newton's iteration. */ x += x * (1.0 - r * npwr(x, n)) / static_cast<double>(n); if (a.hi < 0.0) x = -x; return 1.0/x; }
static void sincos_taylor(const dd_real &a, dd_real &sin_a, dd_real &cos_a) { if (a.is_zero()) { sin_a = 0.0; cos_a = 1.0; return; } sin_a = sin_taylor(a); cos_a = sqrt(1.0 - sqr(sin_a)); }
dd_real asin(const dd_real &a) { dd_real abs_a = abs(a); if (abs_a > 1.0) { dd_real::abort("(dd_real::asin): Argument out of domain."); return dd_real::_nan; } if (abs_a.is_one()) { return (a.is_positive()) ? dd_real::_pi2 : -dd_real::_pi2; } return atan2(a, sqrt(1.0 - sqr(a))); }
dd_real acos(const dd_real &a) { dd_real abs_a = abs(a); if (abs_a > 1.0) { dd_real::abort("(dd_real::acos): Argument out of domain."); return dd_real::_nan; } if (abs_a.is_one()) { return (a.is_positive()) ? dd_real(0.0) : dd_real::_pi; } return atan2(sqrt(1.0 - sqr(a)), a); }
/* Computes the square root of the double-double number dd. NOTE: dd must be a non-negative number. */ QD_API dd_real sqrt(const dd_real &a) { /* Strategy: Use Karp's trick: if x is an approximation to sqrt(a), then sqrt(a) = a*x + [a - (a*x)^2] * x / 2 (approx) The approximation is accurate to twice the accuracy of x. Also, the multiplication (a*x) and [-]*x can be done with only half the precision. */ if (a.is_zero()) return 0.0; if (a.is_negative()) { dd_real::abort("(dd_real::sqrt): Negative argument."); return dd_real::_nan; } double x = 1.0 / std::sqrt(a.hi); double ax = a.hi * x; return dd_real::add(ax, (a - dd_real::sqr(ax)).hi * (x * 0.5)); }
static dd_real cos_taylor(const dd_real &a) { const double thresh = 0.5 * dd_real::_eps; dd_real r, s, t, x; if (a.is_zero()) { return 1.0; } x = -sqr(a); r = x; s = 1.0 + mul_pwr2(r, 0.5); int i = 1; do { r *= x; t = r * dd_real(inv_fact[i][0], inv_fact[i][1]); s += t; i += 2; } while (i < n_inv_fact && std::abs(to_double(t)) > thresh); return s; }
/* Computes sin(a) using Taylor series. Assumes |a| <= pi/32. */ static dd_real sin_taylor(const dd_real &a) { const double thresh = 0.5 * std::abs(to_double(a)) * dd_real::_eps; dd_real r, s, t, x; if (a.is_zero()) { return 0.0; } int i = 0; x = -sqr(a); s = a; r = a; do { r *= x; t = r * dd_real(inv_fact[i][0], inv_fact[i][1]); s += t; i += 2; } while (i < n_inv_fact && std::abs(to_double(t)) > thresh); return s; }
static dd_real cos_taylor(const dd_real &a) { const double thresh = 1.0e-35 * std::abs(to_double(a)); dd_real t; /* Term being added. */ dd_real s; /* Current partial sum. */ dd_real x; /* = -sqr(a) */ double m; if (a.is_zero()) { return 1.0; } x = -sqr(a); t = 0.5 * x; s = 1.0 + t; m = 2.0; do { m += 2.0; t *= x; t /= (m*(m-1.0)); s += t; } while (std::abs(to_double(t)) > thresh); return s; }
dd_real sin(const dd_real &a) { /* Strategy. To compute sin(x), we choose integers a, b so that x = s + a * (pi/2) + b * (pi/16) and |s| <= pi/32. Using the fact that sin(pi/16) = 0.5 * sqrt(2 - sqrt(2 + sqrt(2))) we can compute sin(x) from sin(s), cos(s). This greatly increases the convergence of the sine Taylor series. */ if (a.is_zero()) { return 0.0; } /* First reduce modulo 2*pi so that |r| <= pi. */ dd_real r = drem(a, dd_real::_2pi); /* Now reduce by modulo pi/2 and then by pi/16 so that we obtain numbers a, b, and t. */ dd_real t; dd_real sin_t, cos_t; dd_real s, c; int j = to_int(divrem(r, dd_real::_pi2, t)); int abs_j = std::abs(j); int k = to_int(divrem(t, dd_real::_pi16, t)); int abs_k = std::abs(k); if (abs_j > 2) { dd_real::abort("(dd_real::sin): Cannot reduce modulo pi/2."); return dd_real::_nan; } if (abs_k > 4) { dd_real::abort("(dd_real::sin): Cannot reduce modulo pi/16."); return dd_real::_nan; } if (abs_j == 0) { if (k == 0) { r = sin_taylor(t); } else if (k > 0) { dd_real u = dd_real::cos_table[abs_k-1]; dd_real v = dd_real::sin_table[abs_k-1]; dd_real sin_t, cos_t; sincos_taylor(t, sin_t, cos_t); r = u * sin_t + v * cos_t; } else { dd_real u = dd_real::cos_table[abs_k-1]; dd_real v = dd_real::sin_table[abs_k-1]; dd_real sin_t, cos_t; sincos_taylor(t, sin_t, cos_t); r = u * sin_t - v * cos_t; } } else if (j == 1) { if (k == 0) { r = cos_taylor(t); } else if (k > 0) { dd_real u = dd_real::cos_table[abs_k-1]; dd_real v = dd_real::sin_table[abs_k-1]; dd_real sin_t, cos_t; sincos_taylor(t, sin_t, cos_t); r = u * cos_t - v * sin_t; } else { dd_real u = dd_real::cos_table[abs_k-1]; dd_real v = dd_real::sin_table[abs_k-1]; dd_real sin_t, cos_t; sincos_taylor(t, sin_t, cos_t); r = u * cos_t + v * sin_t; } } else if (j == -1) { if (k == 0) { r = -cos_taylor(t); } else if (k > 0) { dd_real u = dd_real::cos_table[abs_k-1]; dd_real v = dd_real::sin_table[abs_k-1]; dd_real sin_t, cos_t; sincos_taylor(t, sin_t, cos_t); r = v * sin_t - u * cos_t; } else if (k < 0) { dd_real u = dd_real::cos_table[abs_k-1]; dd_real v = dd_real::sin_table[abs_k-1]; dd_real sin_t, cos_t; sincos_taylor(t, sin_t, cos_t); r = -u * cos_t - v * sin_t; } } else { if (k == 0) { r = -sin_taylor(t); } else if (k > 0) { dd_real u = dd_real::cos_table[abs_k-1]; dd_real v = dd_real::sin_table[abs_k-1]; dd_real sin_t, cos_t; sincos_taylor(t, sin_t, cos_t); r = -u * sin_t - v * cos_t; } else { dd_real u = dd_real::cos_table[abs_k-1]; dd_real v = dd_real::sin_table[abs_k-1]; dd_real sin_t, cos_t; sincos_taylor(t, sin_t, cos_t); r = v * cos_t - u * sin_t; } } return r; }
dd_real cos(const dd_real &a) { if (a.is_zero()) { return 1.0; } // approximately reduce modulo 2*pi dd_real z = nint(a / dd_real::_2pi); dd_real r = a - z * dd_real::_2pi; // approximately reduce modulo pi/2 and then modulo pi/16 dd_real t; double q = std::floor(r.x[0] / dd_real::_pi2.x[0] + 0.5); t = r - dd_real::_pi2 * q; int j = static_cast<int>(q); q = std::floor(t.x[0] / _pi16.x[0] + 0.5); t -= _pi16 * q; int k = static_cast<int>(q); int abs_k = std::abs(k); if (j < -2 || j > 2) { dd_real::error("(dd_real::cos): Cannot reduce modulo pi/2."); return dd_real::_nan; } if (abs_k > 4) { dd_real::error("(dd_real::cos): Cannot reduce modulo pi/16."); return dd_real::_nan; } if (k == 0) { switch (j) { case 0: return cos_taylor(t); case 1: return -sin_taylor(t); case -1: return sin_taylor(t); default: return -cos_taylor(t); } } dd_real sin_t, cos_t; sincos_taylor(t, sin_t, cos_t); dd_real u(cos_table[abs_k-1][0], cos_table[abs_k-1][1]); dd_real v(sin_table[abs_k-1][0], sin_table[abs_k-1][1]); if (j == 0) { if (k > 0) { r = u * cos_t - v * sin_t; } else { r = u * cos_t + v * sin_t; } } else if (j == 1) { if (k > 0) { r = - u * sin_t - v * cos_t; } else { r = v * cos_t - u * sin_t; } } else if (j == -1) { if (k > 0) { r = u * sin_t + v * cos_t; } else { r = u * sin_t - v * cos_t; } } else { if (k > 0) { r = v * sin_t - u * cos_t; } else { r = - u * cos_t - v * sin_t; } } return r; }
void sincos(const dd_real &a, dd_real &sin_a, dd_real &cos_a) { if (a.is_zero()) { sin_a = 0.0; cos_a = 1.0; return; } // approximately reduce modulo 2*pi dd_real z = nint(a / dd_real::_2pi); dd_real r = a - dd_real::_2pi * z; // approximately reduce module pi/2 and pi/16 dd_real t; double q = std::floor(r.x[0] / dd_real::_pi2.x[0] + 0.5); t = r - dd_real::_pi2 * q; int j = static_cast<int>(q); int abs_j = std::abs(j); q = std::floor(t.x[0] / _pi16.x[0] + 0.5); t -= _pi16 * q; int k = static_cast<int>(q); int abs_k = std::abs(k); if (abs_j > 2) { dd_real::error("(dd_real::sincos): Cannot reduce modulo pi/2."); cos_a = sin_a = dd_real::_nan; return; } if (abs_k > 4) { dd_real::error("(dd_real::sincos): Cannot reduce modulo pi/16."); cos_a = sin_a = dd_real::_nan; return; } dd_real sin_t, cos_t; dd_real s, c; sincos_taylor(t, sin_t, cos_t); if (abs_k == 0) { s = sin_t; c = cos_t; } else { dd_real u(cos_table[abs_k-1][0], cos_table[abs_k-1][1]); dd_real v(sin_table[abs_k-1][0], sin_table[abs_k-1][1]); if (k > 0) { s = u * sin_t + v * cos_t; c = u * cos_t - v * sin_t; } else { s = u * sin_t - v * cos_t; c = u * cos_t + v * sin_t; } } if (abs_j == 0) { sin_a = s; cos_a = c; } else if (j == 1) { sin_a = c; cos_a = -s; } else if (j == -1) { sin_a = -c; cos_a = s; } else { sin_a = -s; cos_a = -c; } }
bool Teuchos::operator&&(const dd_real &a, const dd_real &b) { return !a.is_zero() && !b.is_zero(); }
dd_real sin(const dd_real &a) { /* Strategy. To compute sin(x), we choose integers a, b so that x = s + a * (pi/2) + b * (pi/16) and |s| <= pi/32. Using the fact that sin(pi/16) = 0.5 * sqrt(2 - sqrt(2 + sqrt(2))) we can compute sin(x) from sin(s), cos(s). This greatly increases the convergence of the sine Taylor series. */ if (a.is_zero()) { return 0.0; } // approximately reduce modulo 2*pi dd_real z = nint(a / dd_real::_2pi); dd_real r = a - dd_real::_2pi * z; // approximately reduce modulo pi/2 and then modulo pi/16. dd_real t; double q = std::floor(r.x[0] / dd_real::_pi2.x[0] + 0.5); t = r - dd_real::_pi2 * q; int j = static_cast<int>(q); q = std::floor(t.x[0] / _pi16.x[0] + 0.5); t -= _pi16 * q; int k = static_cast<int>(q); int abs_k = std::abs(k); if (j < -2 || j > 2) { dd_real::error("(dd_real::sin): Cannot reduce modulo pi/2."); return dd_real::_nan; } if (abs_k > 4) { dd_real::error("(dd_real::sin): Cannot reduce modulo pi/16."); return dd_real::_nan; } if (k == 0) { switch (j) { case 0: return sin_taylor(t); case 1: return cos_taylor(t); case -1: return -cos_taylor(t); default: return -sin_taylor(t); } } dd_real u(cos_table[abs_k-1][0], cos_table[abs_k-1][1]); dd_real v(sin_table[abs_k-1][0], sin_table[abs_k-1][1]); dd_real sin_t, cos_t; sincos_taylor(t, sin_t, cos_t); if (j == 0) { if (k > 0) { r = u * sin_t + v * cos_t; } else { r = u * sin_t - v * cos_t; } } else if (j == 1) { if (k > 0) { r = u * cos_t - v * sin_t; } else { r = u * cos_t + v * sin_t; } } else if (j == -1) { if (k > 0) { r = v * sin_t - u * cos_t; } else if (k < 0) { r = -u * cos_t - v * sin_t; } } else { if (k > 0) { r = -u * sin_t - v * cos_t; } else { r = v * cos_t - u * sin_t; } } return r; }