| /* Implementation of gamma function according to ISO C. |
| Copyright (C) 1997-2018 Free Software Foundation, Inc. |
| This file is part of the GNU C Library. |
| Contributed by Ulrich Drepper <drepper@cygnus.com>, 1997 and |
| Jakub Jelinek <jj@ultra.linux.cz, 1999. |
| |
| The GNU C Library is free software; you can redistribute it and/or |
| modify it under the terms of the GNU Lesser General Public |
| License as published by the Free Software Foundation; either |
| version 2.1 of the License, or (at your option) any later version. |
| |
| The GNU C Library is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| Lesser General Public License for more details. |
| |
| You should have received a copy of the GNU Lesser General Public |
| License along with the GNU C Library; if not, see |
| <http://www.gnu.org/licenses/>. */ |
| |
| #include "quadmath-imp.h" |
| __float128 |
| tgammaq (__float128 x) |
| { |
| int sign; |
| __float128 ret; |
| ret = __quadmath_gammaq_r (x, &sign); |
| return sign < 0 ? -ret : ret; |
| } |
| |
| /* Coefficients B_2k / 2k(2k-1) of x^-(2k-1) inside exp in Stirling's |
| approximation to gamma function. */ |
| |
| static const __float128 gamma_coeff[] = |
| { |
| 0x1.5555555555555555555555555555p-4Q, |
| -0xb.60b60b60b60b60b60b60b60b60b8p-12Q, |
| 0x3.4034034034034034034034034034p-12Q, |
| -0x2.7027027027027027027027027028p-12Q, |
| 0x3.72a3c5631fe46ae1d4e700dca8f2p-12Q, |
| -0x7.daac36664f1f207daac36664f1f4p-12Q, |
| 0x1.a41a41a41a41a41a41a41a41a41ap-8Q, |
| -0x7.90a1b2c3d4e5f708192a3b4c5d7p-8Q, |
| 0x2.dfd2c703c0cfff430edfd2c703cp-4Q, |
| -0x1.6476701181f39edbdb9ce625987dp+0Q, |
| 0xd.672219167002d3a7a9c886459cp+0Q, |
| -0x9.cd9292e6660d55b3f712eb9e07c8p+4Q, |
| 0x8.911a740da740da740da740da741p+8Q, |
| -0x8.d0cc570e255bf59ff6eec24b49p+12Q, |
| }; |
| |
| #define NCOEFF (sizeof (gamma_coeff) / sizeof (gamma_coeff[0])) |
| |
| /* Return gamma (X), for positive X less than 1775, in the form R * |
| 2^(*EXP2_ADJ), where R is the return value and *EXP2_ADJ is set to |
| avoid overflow or underflow in intermediate calculations. */ |
| |
| static __float128 |
| gammal_positive (__float128 x, int *exp2_adj) |
| { |
| int local_signgam; |
| if (x < 0.5Q) |
| { |
| *exp2_adj = 0; |
| return expq (__quadmath_lgammaq_r (x + 1, &local_signgam)) / x; |
| } |
| else if (x <= 1.5Q) |
| { |
| *exp2_adj = 0; |
| return expq (__quadmath_lgammaq_r (x, &local_signgam)); |
| } |
| else if (x < 12.5Q) |
| { |
| /* Adjust into the range for using exp (lgamma). */ |
| *exp2_adj = 0; |
| __float128 n = ceilq (x - 1.5Q); |
| __float128 x_adj = x - n; |
| __float128 eps; |
| __float128 prod = __quadmath_gamma_productq (x_adj, 0, n, &eps); |
| return (expq (__quadmath_lgammaq_r (x_adj, &local_signgam)) |
| * prod * (1 + eps)); |
| } |
| else |
| { |
| __float128 eps = 0; |
| __float128 x_eps = 0; |
| __float128 x_adj = x; |
| __float128 prod = 1; |
| if (x < 24) |
| { |
| /* Adjust into the range for applying Stirling's |
| approximation. */ |
| __float128 n = ceilq (24 - x); |
| x_adj = x + n; |
| x_eps = (x - (x_adj - n)); |
| prod = __quadmath_gamma_productq (x_adj - n, x_eps, n, &eps); |
| } |
| /* The result is now gamma (X_ADJ + X_EPS) / (PROD * (1 + EPS)). |
| Compute gamma (X_ADJ + X_EPS) using Stirling's approximation, |
| starting by computing pow (X_ADJ, X_ADJ) with a power of 2 |
| factored out. */ |
| __float128 exp_adj = -eps; |
| __float128 x_adj_int = roundq (x_adj); |
| __float128 x_adj_frac = x_adj - x_adj_int; |
| int x_adj_log2; |
| __float128 x_adj_mant = frexpq (x_adj, &x_adj_log2); |
| if (x_adj_mant < M_SQRT1_2q) |
| { |
| x_adj_log2--; |
| x_adj_mant *= 2; |
| } |
| *exp2_adj = x_adj_log2 * (int) x_adj_int; |
| __float128 ret = (powq (x_adj_mant, x_adj) |
| * exp2q (x_adj_log2 * x_adj_frac) |
| * expq (-x_adj) |
| * sqrtq (2 * M_PIq / x_adj) |
| / prod); |
| exp_adj += x_eps * logq (x_adj); |
| __float128 bsum = gamma_coeff[NCOEFF - 1]; |
| __float128 x_adj2 = x_adj * x_adj; |
| for (size_t i = 1; i <= NCOEFF - 1; i++) |
| bsum = bsum / x_adj2 + gamma_coeff[NCOEFF - 1 - i]; |
| exp_adj += bsum / x_adj; |
| return ret + ret * expm1q (exp_adj); |
| } |
| } |
| |
| __float128 |
| __quadmath_gammaq_r (__float128 x, int *signgamp) |
| { |
| int64_t hx; |
| uint64_t lx; |
| __float128 ret; |
| |
| GET_FLT128_WORDS64 (hx, lx, x); |
| |
| if (((hx & 0x7fffffffffffffffLL) | lx) == 0) |
| { |
| /* Return value for x == 0 is Inf with divide by zero exception. */ |
| *signgamp = 0; |
| return 1.0 / x; |
| } |
| if (hx < 0 && (uint64_t) hx < 0xffff000000000000ULL && rintq (x) == x) |
| { |
| /* Return value for integer x < 0 is NaN with invalid exception. */ |
| *signgamp = 0; |
| return (x - x) / (x - x); |
| } |
| if (hx == 0xffff000000000000ULL && lx == 0) |
| { |
| /* x == -Inf. According to ISO this is NaN. */ |
| *signgamp = 0; |
| return x - x; |
| } |
| if ((hx & 0x7fff000000000000ULL) == 0x7fff000000000000ULL) |
| { |
| /* Positive infinity (return positive infinity) or NaN (return |
| NaN). */ |
| *signgamp = 0; |
| return x + x; |
| } |
| |
| if (x >= 1756) |
| { |
| /* Overflow. */ |
| *signgamp = 0; |
| return FLT128_MAX * FLT128_MAX; |
| } |
| else |
| { |
| SET_RESTORE_ROUNDF128 (FE_TONEAREST); |
| if (x > 0) |
| { |
| *signgamp = 0; |
| int exp2_adj; |
| ret = gammal_positive (x, &exp2_adj); |
| ret = scalbnq (ret, exp2_adj); |
| } |
| else if (x >= -FLT128_EPSILON / 4) |
| { |
| *signgamp = 0; |
| ret = 1 / x; |
| } |
| else |
| { |
| __float128 tx = truncq (x); |
| *signgamp = (tx == 2 * truncq (tx / 2)) ? -1 : 1; |
| if (x <= -1775) |
| /* Underflow. */ |
| ret = FLT128_MIN * FLT128_MIN; |
| else |
| { |
| __float128 frac = tx - x; |
| if (frac > 0.5Q) |
| frac = 1 - frac; |
| __float128 sinpix = (frac <= 0.25Q |
| ? sinq (M_PIq * frac) |
| : cosq (M_PIq * (0.5Q - frac))); |
| int exp2_adj; |
| ret = M_PIq / (-x * sinpix |
| * gammal_positive (-x, &exp2_adj)); |
| ret = scalbnq (ret, -exp2_adj); |
| math_check_force_underflow_nonneg (ret); |
| } |
| } |
| } |
| if (isinfq (ret) && x != 0) |
| { |
| if (*signgamp < 0) |
| return -(-copysignq (FLT128_MAX, ret) * FLT128_MAX); |
| else |
| return copysignq (FLT128_MAX, ret) * FLT128_MAX; |
| } |
| else if (ret == 0) |
| { |
| if (*signgamp < 0) |
| return -(-copysignq (FLT128_MIN, ret) * FLT128_MIN); |
| else |
| return copysignq (FLT128_MIN, ret) * FLT128_MIN; |
| } |
| else |
| return ret; |
| } |