|  | /* Implementation of the degree trignometric functions COSD, SIND, TAND. | 
|  | Copyright (C) 2020-2021 Free Software Foundation, Inc. | 
|  | Contributed by Steven G. Kargl <kargl@gcc.gnu.org> | 
|  | and Fritz Reese <foreese@gcc.gnu.org> | 
|  |  | 
|  | This file is part of the GNU Fortran runtime library (libgfortran). | 
|  |  | 
|  | Libgfortran is free software; you can redistribute it and/or | 
|  | modify it under the terms of the GNU General Public | 
|  | License as published by the Free Software Foundation; either | 
|  | version 3 of the License, or (at your option) any later version. | 
|  |  | 
|  | Libgfortran 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 General Public License for more details. | 
|  |  | 
|  | Under Section 7 of GPL version 3, you are granted additional | 
|  | permissions described in the GCC Runtime Library Exception, version | 
|  | 3.1, as published by the Free Software Foundation. | 
|  |  | 
|  | You should have received a copy of the GNU General Public License and | 
|  | a copy of the GCC Runtime Library Exception along with this program; | 
|  | see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see | 
|  | <http://www.gnu.org/licenses/>.  */ | 
|  |  | 
|  |  | 
|  | /* | 
|  |  | 
|  | This file is included from both the FE and the runtime library code. | 
|  | Operations are generalized using GMP/MPFR functions. When included from | 
|  | libgfortran, these should be overridden using macros which will use native | 
|  | operations conforming to the same API. From the FE, the GMP/MPFR functions can | 
|  | be used as-is. | 
|  |  | 
|  | The following macros are used and must be defined, unless listed as [optional]: | 
|  |  | 
|  | FTYPE | 
|  | Type name for the real-valued parameter. | 
|  | Variables of this type are constructed/destroyed using mpfr_init() | 
|  | and mpfr_clear. | 
|  |  | 
|  | RETTYPE | 
|  | Return type of the functions. | 
|  |  | 
|  | RETURN(x) | 
|  | Insert code to return a value. | 
|  | The parameter x is the result variable, which was also the input parameter. | 
|  |  | 
|  | ITYPE | 
|  | Type name for integer types. | 
|  |  | 
|  | SIND, COSD, TRIGD | 
|  | Names for the degree-valued trig functions defined by this module. | 
|  |  | 
|  | ENABLE_SIND, ENABLE_COSD, ENABLE_TAND | 
|  | Whether the degree-valued trig functions can be enabled. | 
|  |  | 
|  | ERROR_RETURN(f, k, x) | 
|  | If ENABLE_<xxx>D is not defined, this is substituted to assert an | 
|  | error condition for function f, kind k, and parameter x. | 
|  | The function argument is one of {sind, cosd, tand}. | 
|  |  | 
|  | ISFINITE(x) | 
|  | Whether x is a regular number or zero (not inf or NaN). | 
|  |  | 
|  | D2R(x) | 
|  | Convert x from radians to degrees. | 
|  |  | 
|  | SET_COSD30(x) | 
|  | Set x to COSD(30), or equivalently, SIND(60). | 
|  |  | 
|  | TINY_LITERAL [optional] | 
|  | Value subtracted from 1 to cause raise INEXACT for COSD(x) for x << 1. | 
|  | If not set, COSD(x) for x <= COSD_SMALL_LITERAL simply returns 1. | 
|  |  | 
|  | COSD_SMALL_LITERAL [optional] | 
|  | Value such that x <= COSD_SMALL_LITERAL implies COSD(x) = 1 to within the | 
|  | precision of FTYPE. If not set, this condition is not checked. | 
|  |  | 
|  | SIND_SMALL_LITERAL [optional] | 
|  | Value such that x <= SIND_SMALL_LITERAL implies SIND(x) = D2R(x) to within | 
|  | the precision of FTYPE. If not set, this condition is not checked. | 
|  |  | 
|  | */ | 
|  |  | 
|  |  | 
|  | #ifdef SIND | 
|  | /* Compute sind(x) = sin(x * pi / 180). */ | 
|  |  | 
|  | RETTYPE | 
|  | SIND (FTYPE x) | 
|  | { | 
|  | #ifdef ENABLE_SIND | 
|  | if (ISFINITE (x)) | 
|  | { | 
|  | FTYPE s, one; | 
|  |  | 
|  | /* sin(-x) = - sin(x).  */ | 
|  | mpfr_init (s); | 
|  | mpfr_init_set_ui (one, 1, GFC_RND_MODE); | 
|  | mpfr_copysign (s, one, x, GFC_RND_MODE); | 
|  | mpfr_clear (one); | 
|  |  | 
|  | #ifdef SIND_SMALL_LITERAL | 
|  | /* sin(x) = x as x -> 0; but only for some precisions. */ | 
|  | FTYPE ax; | 
|  | mpfr_init (ax); | 
|  | mpfr_abs (ax, x, GFC_RND_MODE); | 
|  | if (mpfr_cmp_ld (ax, SIND_SMALL_LITERAL) < 0) | 
|  | { | 
|  | D2R (x); | 
|  | mpfr_clear (ax); | 
|  | return x; | 
|  | } | 
|  |  | 
|  | mpfr_swap (x, ax); | 
|  | mpfr_clear (ax); | 
|  |  | 
|  | #else | 
|  | mpfr_abs (x, x, GFC_RND_MODE); | 
|  | #endif /* SIND_SMALL_LITERAL */ | 
|  |  | 
|  | /* Reduce angle to x in [0,360].  */ | 
|  | FTYPE period; | 
|  | mpfr_init_set_ui (period, 360, GFC_RND_MODE); | 
|  | mpfr_fmod (x, x, period, GFC_RND_MODE); | 
|  | mpfr_clear (period); | 
|  |  | 
|  | /* Special cases with exact results.  */ | 
|  | ITYPE n; | 
|  | mpz_init (n); | 
|  | if (mpfr_get_z (n, x, GFC_RND_MODE) == 0 && mpz_divisible_ui_p (n, 30)) | 
|  | { | 
|  | /* Flip sign for odd n*pi (x is % 360 so this is only for 180). | 
|  | This respects sgn(sin(x)) = sgn(d/dx sin(x)) = sgn(cos(x)). */ | 
|  | if (mpz_divisible_ui_p (n, 180)) | 
|  | { | 
|  | mpfr_set_ui (x, 0, GFC_RND_MODE); | 
|  | if (mpz_cmp_ui (n, 180) == 0) | 
|  | mpfr_neg (s, s, GFC_RND_MODE); | 
|  | } | 
|  | else if (mpz_divisible_ui_p (n, 90)) | 
|  | mpfr_set_si (x, (mpz_cmp_ui (n, 90) == 0 ? 1 : -1), GFC_RND_MODE); | 
|  | else if (mpz_divisible_ui_p (n, 60)) | 
|  | { | 
|  | SET_COSD30 (x); | 
|  | if (mpz_cmp_ui (n, 180) >= 0) | 
|  | mpfr_neg (x, x, GFC_RND_MODE); | 
|  | } | 
|  | else | 
|  | mpfr_set_ld (x, (mpz_cmp_ui (n, 180) < 0 ? 0.5L : -0.5L), | 
|  | GFC_RND_MODE); | 
|  | } | 
|  |  | 
|  | /* Fold [0,360] into the range [0,45], and compute either SIN() or | 
|  | COS() depending on symmetry of shifting into the [0,45] range.  */ | 
|  | else | 
|  | { | 
|  | bool fold_cos = false; | 
|  | if (mpfr_cmp_ui (x, 180) <= 0) | 
|  | { | 
|  | if (mpfr_cmp_ui (x, 90) <= 0) | 
|  | { | 
|  | if (mpfr_cmp_ui (x, 45) > 0) | 
|  | { | 
|  | /* x = COS(D2R(90 - x)) */ | 
|  | mpfr_ui_sub (x, 90, x, GFC_RND_MODE); | 
|  | fold_cos = true; | 
|  | } | 
|  | } | 
|  | else | 
|  | { | 
|  | if (mpfr_cmp_ui (x, 135) <= 0) | 
|  | { | 
|  | mpfr_sub_ui (x, x, 90, GFC_RND_MODE); | 
|  | fold_cos = true; | 
|  | } | 
|  | else | 
|  | mpfr_ui_sub (x, 180, x, GFC_RND_MODE); | 
|  | } | 
|  | } | 
|  |  | 
|  | else if (mpfr_cmp_ui (x, 270) <= 0) | 
|  | { | 
|  | if (mpfr_cmp_ui (x, 225) <= 0) | 
|  | mpfr_sub_ui (x, x, 180, GFC_RND_MODE); | 
|  | else | 
|  | { | 
|  | mpfr_ui_sub (x, 270, x, GFC_RND_MODE); | 
|  | fold_cos = true; | 
|  | } | 
|  | mpfr_neg (s, s, GFC_RND_MODE); | 
|  | } | 
|  |  | 
|  | else | 
|  | { | 
|  | if (mpfr_cmp_ui (x, 315) <= 0) | 
|  | { | 
|  | mpfr_sub_ui (x, x, 270, GFC_RND_MODE); | 
|  | fold_cos = true; | 
|  | } | 
|  | else | 
|  | mpfr_ui_sub (x, 360, x, GFC_RND_MODE); | 
|  | mpfr_neg (s, s, GFC_RND_MODE); | 
|  | } | 
|  |  | 
|  | D2R (x); | 
|  |  | 
|  | if (fold_cos) | 
|  | mpfr_cos (x, x, GFC_RND_MODE); | 
|  | else | 
|  | mpfr_sin (x, x, GFC_RND_MODE); | 
|  | } | 
|  |  | 
|  | mpfr_mul (x, x, s, GFC_RND_MODE); | 
|  | mpz_clear (n); | 
|  | mpfr_clear (s); | 
|  | } | 
|  |  | 
|  | /* Return NaN for +-Inf and NaN and raise exception.  */ | 
|  | else | 
|  | mpfr_sub (x, x, x, GFC_RND_MODE); | 
|  |  | 
|  | RETURN (x); | 
|  |  | 
|  | #else | 
|  | ERROR_RETURN(sind, KIND, x); | 
|  | #endif // ENABLE_SIND | 
|  | } | 
|  | #endif // SIND | 
|  |  | 
|  |  | 
|  | #ifdef COSD | 
|  | /* Compute cosd(x) = cos(x * pi / 180).  */ | 
|  |  | 
|  | RETTYPE | 
|  | COSD (FTYPE x) | 
|  | { | 
|  | #ifdef ENABLE_COSD | 
|  | #if defined(TINY_LITERAL) && defined(COSD_SMALL_LITERAL) | 
|  | static const volatile FTYPE tiny = TINY_LITERAL; | 
|  | #endif | 
|  |  | 
|  | if (ISFINITE (x)) | 
|  | { | 
|  | #ifdef COSD_SMALL_LITERAL | 
|  | FTYPE ax; | 
|  | mpfr_init (ax); | 
|  |  | 
|  | mpfr_abs (ax, x, GFC_RND_MODE); | 
|  | /* No spurious underflows!.  In radians, cos(x) = 1-x*x/2 as x -> 0.  */ | 
|  | if (mpfr_cmp_ld (ax, COSD_SMALL_LITERAL) <= 0) | 
|  | { | 
|  | mpfr_set_ui (x, 1, GFC_RND_MODE); | 
|  | #ifdef TINY_LITERAL | 
|  | /* Cause INEXACT.  */ | 
|  | if (!mpfr_zero_p (ax)) | 
|  | mpfr_sub_d (x, x, tiny, GFC_RND_MODE); | 
|  | #endif | 
|  |  | 
|  | mpfr_clear (ax); | 
|  | return x; | 
|  | } | 
|  |  | 
|  | mpfr_swap (x, ax); | 
|  | mpfr_clear (ax); | 
|  | #else | 
|  | mpfr_abs (x, x, GFC_RND_MODE); | 
|  | #endif /* COSD_SMALL_LITERAL */ | 
|  |  | 
|  | /* Reduce angle to ax in [0,360].  */ | 
|  | FTYPE period; | 
|  | mpfr_init_set_ui (period, 360, GFC_RND_MODE); | 
|  | mpfr_fmod (x, x, period, GFC_RND_MODE); | 
|  | mpfr_clear (period); | 
|  |  | 
|  | /* Special cases with exact results. | 
|  | Return negative zero for cosd(270) for consistency with libm cos().  */ | 
|  | ITYPE n; | 
|  | mpz_init (n); | 
|  | if (mpfr_get_z (n, x, GFC_RND_MODE) == 0 && mpz_divisible_ui_p (n, 30)) | 
|  | { | 
|  | if (mpz_divisible_ui_p (n, 180)) | 
|  | mpfr_set_si (x, (mpz_cmp_ui (n, 180) == 0 ? -1 : 1), | 
|  | GFC_RND_MODE); | 
|  | else if (mpz_divisible_ui_p (n, 90)) | 
|  | mpfr_set_zero (x, 0); | 
|  | else if (mpz_divisible_ui_p (n, 60)) | 
|  | { | 
|  | mpfr_set_ld (x, 0.5, GFC_RND_MODE); | 
|  | if (mpz_cmp_ui (n, 60) != 0 && mpz_cmp_ui (n, 300) != 0) | 
|  | mpfr_neg (x, x, GFC_RND_MODE); | 
|  | } | 
|  | else | 
|  | { | 
|  | SET_COSD30 (x); | 
|  | if (mpz_cmp_ui (n, 30) != 0 && mpz_cmp_ui (n, 330) != 0) | 
|  | mpfr_neg (x, x, GFC_RND_MODE); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Fold [0,360] into the range [0,45], and compute either SIN() or | 
|  | COS() depending on symmetry of shifting into the [0,45] range.  */ | 
|  | else | 
|  | { | 
|  | bool neg = false; | 
|  | bool fold_sin = false; | 
|  | if (mpfr_cmp_ui (x, 180) <= 0) | 
|  | { | 
|  | if (mpfr_cmp_ui (x, 90) <= 0) | 
|  | { | 
|  | if (mpfr_cmp_ui (x, 45) > 0) | 
|  | { | 
|  | mpfr_ui_sub (x, 90, x, GFC_RND_MODE); | 
|  | fold_sin = true; | 
|  | } | 
|  | } | 
|  | else | 
|  | { | 
|  | if (mpfr_cmp_ui (x, 135) <= 0) | 
|  | { | 
|  | mpfr_sub_ui (x, x, 90, GFC_RND_MODE); | 
|  | fold_sin = true; | 
|  | } | 
|  | else | 
|  | mpfr_ui_sub (x, 180, x, GFC_RND_MODE); | 
|  | neg = true; | 
|  | } | 
|  | } | 
|  |  | 
|  | else if (mpfr_cmp_ui (x, 270) <= 0) | 
|  | { | 
|  | if (mpfr_cmp_ui (x, 225) <= 0) | 
|  | mpfr_sub_ui (x, x, 180, GFC_RND_MODE); | 
|  | else | 
|  | { | 
|  | mpfr_ui_sub (x, 270, x, GFC_RND_MODE); | 
|  | fold_sin = true; | 
|  | } | 
|  | neg = true; | 
|  | } | 
|  |  | 
|  | else | 
|  | { | 
|  | if (mpfr_cmp_ui (x, 315) <= 0) | 
|  | { | 
|  | mpfr_sub_ui (x, x, 270, GFC_RND_MODE); | 
|  | fold_sin = true; | 
|  | } | 
|  | else | 
|  | mpfr_ui_sub (x, 360, x, GFC_RND_MODE); | 
|  | } | 
|  |  | 
|  | D2R (x); | 
|  |  | 
|  | if (fold_sin) | 
|  | mpfr_sin (x, x, GFC_RND_MODE); | 
|  | else | 
|  | mpfr_cos (x, x, GFC_RND_MODE); | 
|  |  | 
|  | if (neg) | 
|  | mpfr_neg (x, x, GFC_RND_MODE); | 
|  | } | 
|  |  | 
|  | mpz_clear (n); | 
|  | } | 
|  |  | 
|  | /* Return NaN for +-Inf and NaN and raise exception.  */ | 
|  | else | 
|  | mpfr_sub (x, x, x, GFC_RND_MODE); | 
|  |  | 
|  | RETURN (x); | 
|  |  | 
|  | #else | 
|  | ERROR_RETURN(cosd, KIND, x); | 
|  | #endif // ENABLE_COSD | 
|  | } | 
|  | #endif // COSD | 
|  |  | 
|  |  | 
|  | #ifdef TAND | 
|  | /* Compute tand(x) = tan(x * pi / 180).  */ | 
|  |  | 
|  | RETTYPE | 
|  | TAND (FTYPE x) | 
|  | { | 
|  | #ifdef ENABLE_TAND | 
|  | if (ISFINITE (x)) | 
|  | { | 
|  | FTYPE s, one; | 
|  |  | 
|  | /* tan(-x) = - tan(x).  */ | 
|  | mpfr_init (s); | 
|  | mpfr_init_set_ui (one, 1, GFC_RND_MODE); | 
|  | mpfr_copysign (s, one, x, GFC_RND_MODE); | 
|  | mpfr_clear (one); | 
|  |  | 
|  | #ifdef SIND_SMALL_LITERAL | 
|  | /* tan(x) = x as x -> 0; but only for some precisions. */ | 
|  | FTYPE ax; | 
|  | mpfr_init (ax); | 
|  | mpfr_abs (ax, x, GFC_RND_MODE); | 
|  | if (mpfr_cmp_ld (ax, SIND_SMALL_LITERAL) < 0) | 
|  | { | 
|  | D2R (x); | 
|  | mpfr_clear (ax); | 
|  | return x; | 
|  | } | 
|  |  | 
|  | mpfr_swap (x, ax); | 
|  | mpfr_clear (ax); | 
|  |  | 
|  | #else | 
|  | mpfr_abs (x, x, GFC_RND_MODE); | 
|  | #endif /* SIND_SMALL_LITERAL */ | 
|  |  | 
|  | /* Reduce angle to x in [0,360].  */ | 
|  | FTYPE period; | 
|  | mpfr_init_set_ui (period, 360, GFC_RND_MODE); | 
|  | mpfr_fmod (x, x, period, GFC_RND_MODE); | 
|  | mpfr_clear (period); | 
|  |  | 
|  | /* Special cases with exact results. */ | 
|  | ITYPE n; | 
|  | mpz_init (n); | 
|  | if (mpfr_get_z (n, x, GFC_RND_MODE) == 0 && mpz_divisible_ui_p (n, 45)) | 
|  | { | 
|  | if (mpz_divisible_ui_p (n, 180)) | 
|  | mpfr_set_zero (x, 0); | 
|  |  | 
|  | /* Though mathematically NaN is more appropriate for tan(n*90), | 
|  | returning +/-Inf offers the advantage that 1/tan(n*90) returns 0, | 
|  | which is mathematically sound. In fact we rely on this behavior | 
|  | to implement COTAND(x) = 1 / TAND(x). | 
|  | */ | 
|  | else if (mpz_divisible_ui_p (n, 90)) | 
|  | mpfr_set_inf (x, mpz_cmp_ui (n, 90) == 0 ? 0 : 1); | 
|  |  | 
|  | else | 
|  | { | 
|  | mpfr_set_ui (x, 1, GFC_RND_MODE); | 
|  | if (mpz_cmp_ui (n, 45) != 0 && mpz_cmp_ui (n, 225) != 0) | 
|  | mpfr_neg (x, x, GFC_RND_MODE); | 
|  | } | 
|  | } | 
|  |  | 
|  | else | 
|  | { | 
|  | /* Fold [0,360] into the range [0,90], and compute TAN().  */ | 
|  | if (mpfr_cmp_ui (x, 180) <= 0) | 
|  | { | 
|  | if (mpfr_cmp_ui (x, 90) > 0) | 
|  | { | 
|  | mpfr_ui_sub (x, 180, x, GFC_RND_MODE); | 
|  | mpfr_neg (s, s, GFC_RND_MODE); | 
|  | } | 
|  | } | 
|  | else | 
|  | { | 
|  | if (mpfr_cmp_ui (x, 270) <= 0) | 
|  | { | 
|  | mpfr_sub_ui (x, x, 180, GFC_RND_MODE); | 
|  | } | 
|  | else | 
|  | { | 
|  | mpfr_ui_sub (x, 360, x, GFC_RND_MODE); | 
|  | mpfr_neg (s, s, GFC_RND_MODE); | 
|  | } | 
|  | } | 
|  |  | 
|  | D2R (x); | 
|  | mpfr_tan (x, x, GFC_RND_MODE); | 
|  | } | 
|  |  | 
|  | mpfr_mul (x, x, s, GFC_RND_MODE); | 
|  | mpz_clear (n); | 
|  | mpfr_clear (s); | 
|  | } | 
|  |  | 
|  | /* Return NaN for +-Inf and NaN and raise exception.  */ | 
|  | else | 
|  | mpfr_sub (x, x, x, GFC_RND_MODE); | 
|  |  | 
|  | RETURN (x); | 
|  |  | 
|  | #else | 
|  | ERROR_RETURN(tand, KIND, x); | 
|  | #endif // ENABLE_TAND | 
|  | } | 
|  | #endif // TAND | 
|  |  | 
|  | /* vim: set ft=c: */ |