1/* Implementation of the degree trignometric functions COSD, SIND, TAND.
2   Copyright (C) 2020-2021 Free Software Foundation, Inc.
3   Contributed by Steven G. Kargl <kargl@gcc.gnu.org>
4   and Fritz Reese <foreese@gcc.gnu.org>
5
6This file is part of the GNU Fortran runtime library (libgfortran).
7
8Libgfortran is free software; you can redistribute it and/or
9modify it under the terms of the GNU General Public
10License as published by the Free Software Foundation; either
11version 3 of the License, or (at your option) any later version.
12
13Libgfortran is distributed in the hope that it will be useful,
14but WITHOUT ANY WARRANTY; without even the implied warranty of
15MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16GNU General Public License for more details.
17
18Under Section 7 of GPL version 3, you are granted additional
19permissions described in the GCC Runtime Library Exception, version
203.1, as published by the Free Software Foundation.
21
22You should have received a copy of the GNU General Public License and
23a copy of the GCC Runtime Library Exception along with this program;
24see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
25<http://www.gnu.org/licenses/>.  */
26
27
28/*
29
30This file is included from both the FE and the runtime library code.
31Operations are generalized using GMP/MPFR functions. When included from
32libgfortran, these should be overridden using macros which will use native
33operations conforming to the same API. From the FE, the GMP/MPFR functions can
34be used as-is.
35
36The following macros are used and must be defined, unless listed as [optional]:
37
38FTYPE
39    Type name for the real-valued parameter.
40    Variables of this type are constructed/destroyed using mpfr_init()
41    and mpfr_clear.
42
43RETTYPE
44    Return type of the functions.
45
46RETURN(x)
47    Insert code to return a value.
48    The parameter x is the result variable, which was also the input parameter.
49
50ITYPE
51    Type name for integer types.
52
53SIND, COSD, TRIGD
54    Names for the degree-valued trig functions defined by this module.
55
56ENABLE_SIND, ENABLE_COSD, ENABLE_TAND
57    Whether the degree-valued trig functions can be enabled.
58
59ERROR_RETURN(f, k, x)
60    If ENABLE_<xxx>D is not defined, this is substituted to assert an
61    error condition for function f, kind k, and parameter x.
62    The function argument is one of {sind, cosd, tand}.
63
64ISFINITE(x)
65    Whether x is a regular number or zero (not inf or NaN).
66
67D2R(x)
68    Convert x from radians to degrees.
69
70SET_COSD30(x)
71    Set x to COSD(30), or equivalently, SIND(60).
72
73TINY_LITERAL [optional]
74    Value subtracted from 1 to cause raise INEXACT for COSD(x) for x << 1.
75    If not set, COSD(x) for x <= COSD_SMALL_LITERAL simply returns 1.
76
77COSD_SMALL_LITERAL [optional]
78    Value such that x <= COSD_SMALL_LITERAL implies COSD(x) = 1 to within the
79    precision of FTYPE. If not set, this condition is not checked.
80
81SIND_SMALL_LITERAL [optional]
82    Value such that x <= SIND_SMALL_LITERAL implies SIND(x) = D2R(x) to within
83    the precision of FTYPE. If not set, this condition is not checked.
84
85*/
86
87
88#ifdef SIND
89/* Compute sind(x) = sin(x * pi / 180). */
90
91RETTYPE
92SIND (FTYPE x)
93{
94#ifdef ENABLE_SIND
95  if (ISFINITE (x))
96    {
97      FTYPE s, one;
98
99      /* sin(-x) = - sin(x).  */
100      mpfr_init (s);
101      mpfr_init_set_ui (one, 1, GFC_RND_MODE);
102      mpfr_copysign (s, one, x, GFC_RND_MODE);
103      mpfr_clear (one);
104
105#ifdef SIND_SMALL_LITERAL
106      /* sin(x) = x as x -> 0; but only for some precisions. */
107      FTYPE ax;
108      mpfr_init (ax);
109      mpfr_abs (ax, x, GFC_RND_MODE);
110      if (mpfr_cmp_ld (ax, SIND_SMALL_LITERAL) < 0)
111	{
112	  D2R (x);
113	  mpfr_clear (ax);
114	  return x;
115	}
116
117      mpfr_swap (x, ax);
118      mpfr_clear (ax);
119
120#else
121      mpfr_abs (x, x, GFC_RND_MODE);
122#endif /* SIND_SMALL_LITERAL */
123
124      /* Reduce angle to x in [0,360].  */
125      FTYPE period;
126      mpfr_init_set_ui (period, 360, GFC_RND_MODE);
127      mpfr_fmod (x, x, period, GFC_RND_MODE);
128      mpfr_clear (period);
129
130      /* Special cases with exact results.  */
131      ITYPE n;
132      mpz_init (n);
133      if (mpfr_get_z (n, x, GFC_RND_MODE) == 0 && mpz_divisible_ui_p (n, 30))
134	{
135	  /* Flip sign for odd n*pi (x is % 360 so this is only for 180).
136	     This respects sgn(sin(x)) = sgn(d/dx sin(x)) = sgn(cos(x)). */
137	  if (mpz_divisible_ui_p (n, 180))
138	    {
139	      mpfr_set_ui (x, 0, GFC_RND_MODE);
140	      if (mpz_cmp_ui (n, 180) == 0)
141		mpfr_neg (s, s, GFC_RND_MODE);
142	    }
143	  else if (mpz_divisible_ui_p (n, 90))
144	    mpfr_set_si (x, (mpz_cmp_ui (n, 90) == 0 ? 1 : -1), GFC_RND_MODE);
145	  else if (mpz_divisible_ui_p (n, 60))
146	    {
147	      SET_COSD30 (x);
148	      if (mpz_cmp_ui (n, 180) >= 0)
149		mpfr_neg (x, x, GFC_RND_MODE);
150	    }
151	  else
152	    mpfr_set_ld (x, (mpz_cmp_ui (n, 180) < 0 ? 0.5L : -0.5L),
153			 GFC_RND_MODE);
154	}
155
156      /* Fold [0,360] into the range [0,45], and compute either SIN() or
157         COS() depending on symmetry of shifting into the [0,45] range.  */
158      else
159	{
160	  bool fold_cos = false;
161	  if (mpfr_cmp_ui (x, 180) <= 0)
162	    {
163	      if (mpfr_cmp_ui (x, 90) <= 0)
164		{
165		  if (mpfr_cmp_ui (x, 45) > 0)
166		    {
167		      /* x = COS(D2R(90 - x)) */
168		      mpfr_ui_sub (x, 90, x, GFC_RND_MODE);
169		      fold_cos = true;
170		    }
171		}
172	      else
173		{
174		  if (mpfr_cmp_ui (x, 135) <= 0)
175		    {
176		      mpfr_sub_ui (x, x, 90, GFC_RND_MODE);
177		      fold_cos = true;
178		    }
179		  else
180		    mpfr_ui_sub (x, 180, x, GFC_RND_MODE);
181		}
182	    }
183
184	  else if (mpfr_cmp_ui (x, 270) <= 0)
185	    {
186	      if (mpfr_cmp_ui (x, 225) <= 0)
187		mpfr_sub_ui (x, x, 180, GFC_RND_MODE);
188	      else
189		{
190		  mpfr_ui_sub (x, 270, x, GFC_RND_MODE);
191		  fold_cos = true;
192		}
193	      mpfr_neg (s, s, GFC_RND_MODE);
194	    }
195
196	  else
197	    {
198	      if (mpfr_cmp_ui (x, 315) <= 0)
199		{
200		  mpfr_sub_ui (x, x, 270, GFC_RND_MODE);
201		  fold_cos = true;
202		}
203	      else
204		mpfr_ui_sub (x, 360, x, GFC_RND_MODE);
205	      mpfr_neg (s, s, GFC_RND_MODE);
206	    }
207
208	  D2R (x);
209
210	  if (fold_cos)
211	    mpfr_cos (x, x, GFC_RND_MODE);
212	  else
213	    mpfr_sin (x, x, GFC_RND_MODE);
214	}
215
216      mpfr_mul (x, x, s, GFC_RND_MODE);
217      mpz_clear (n);
218      mpfr_clear (s);
219    }
220
221  /* Return NaN for +-Inf and NaN and raise exception.  */
222  else
223    mpfr_sub (x, x, x, GFC_RND_MODE);
224
225  RETURN (x);
226
227#else
228  ERROR_RETURN(sind, KIND, x);
229#endif // ENABLE_SIND
230}
231#endif // SIND
232
233
234#ifdef COSD
235/* Compute cosd(x) = cos(x * pi / 180).  */
236
237RETTYPE
238COSD (FTYPE x)
239{
240#ifdef ENABLE_COSD
241#if defined(TINY_LITERAL) && defined(COSD_SMALL_LITERAL)
242  static const volatile FTYPE tiny = TINY_LITERAL;
243#endif
244
245  if (ISFINITE (x))
246    {
247#ifdef COSD_SMALL_LITERAL
248      FTYPE ax;
249      mpfr_init (ax);
250
251      mpfr_abs (ax, x, GFC_RND_MODE);
252      /* No spurious underflows!.  In radians, cos(x) = 1-x*x/2 as x -> 0.  */
253      if (mpfr_cmp_ld (ax, COSD_SMALL_LITERAL) <= 0)
254	{
255	  mpfr_set_ui (x, 1, GFC_RND_MODE);
256#ifdef TINY_LITERAL
257	  /* Cause INEXACT.  */
258	  if (!mpfr_zero_p (ax))
259	    mpfr_sub_d (x, x, tiny, GFC_RND_MODE);
260#endif
261
262	  mpfr_clear (ax);
263	  return x;
264	}
265
266      mpfr_swap (x, ax);
267      mpfr_clear (ax);
268#else
269      mpfr_abs (x, x, GFC_RND_MODE);
270#endif /* COSD_SMALL_LITERAL */
271
272      /* Reduce angle to ax in [0,360].  */
273      FTYPE period;
274      mpfr_init_set_ui (period, 360, GFC_RND_MODE);
275      mpfr_fmod (x, x, period, GFC_RND_MODE);
276      mpfr_clear (period);
277
278      /* Special cases with exact results.
279         Return negative zero for cosd(270) for consistency with libm cos().  */
280      ITYPE n;
281      mpz_init (n);
282      if (mpfr_get_z (n, x, GFC_RND_MODE) == 0 && mpz_divisible_ui_p (n, 30))
283	{
284	  if (mpz_divisible_ui_p (n, 180))
285	    mpfr_set_si (x, (mpz_cmp_ui (n, 180) == 0 ? -1 : 1),
286			 GFC_RND_MODE);
287	  else if (mpz_divisible_ui_p (n, 90))
288	    mpfr_set_zero (x, 0);
289	  else if (mpz_divisible_ui_p (n, 60))
290	    {
291	      mpfr_set_ld (x, 0.5, GFC_RND_MODE);
292	      if (mpz_cmp_ui (n, 60) != 0 && mpz_cmp_ui (n, 300) != 0)
293		mpfr_neg (x, x, GFC_RND_MODE);
294	    }
295	  else
296	    {
297	      SET_COSD30 (x);
298	      if (mpz_cmp_ui (n, 30) != 0 && mpz_cmp_ui (n, 330) != 0)
299		mpfr_neg (x, x, GFC_RND_MODE);
300	    }
301	}
302
303      /* Fold [0,360] into the range [0,45], and compute either SIN() or
304         COS() depending on symmetry of shifting into the [0,45] range.  */
305      else
306	{
307	  bool neg = false;
308	  bool fold_sin = false;
309	  if (mpfr_cmp_ui (x, 180) <= 0)
310	    {
311	      if (mpfr_cmp_ui (x, 90) <= 0)
312		{
313		  if (mpfr_cmp_ui (x, 45) > 0)
314		    {
315		      mpfr_ui_sub (x, 90, x, GFC_RND_MODE);
316		      fold_sin = true;
317		    }
318		}
319	      else
320		{
321		  if (mpfr_cmp_ui (x, 135) <= 0)
322		    {
323		      mpfr_sub_ui (x, x, 90, GFC_RND_MODE);
324		      fold_sin = true;
325		    }
326		  else
327		    mpfr_ui_sub (x, 180, x, GFC_RND_MODE);
328		  neg = true;
329		}
330	    }
331
332	  else if (mpfr_cmp_ui (x, 270) <= 0)
333	    {
334	      if (mpfr_cmp_ui (x, 225) <= 0)
335		mpfr_sub_ui (x, x, 180, GFC_RND_MODE);
336	      else
337		{
338		  mpfr_ui_sub (x, 270, x, GFC_RND_MODE);
339		  fold_sin = true;
340		}
341	      neg = true;
342	    }
343
344	  else
345	    {
346	      if (mpfr_cmp_ui (x, 315) <= 0)
347		{
348		  mpfr_sub_ui (x, x, 270, GFC_RND_MODE);
349		  fold_sin = true;
350		}
351	      else
352		mpfr_ui_sub (x, 360, x, GFC_RND_MODE);
353	    }
354
355	  D2R (x);
356
357	  if (fold_sin)
358	    mpfr_sin (x, x, GFC_RND_MODE);
359	  else
360	    mpfr_cos (x, x, GFC_RND_MODE);
361
362	  if (neg)
363	    mpfr_neg (x, x, GFC_RND_MODE);
364	}
365
366      mpz_clear (n);
367    }
368
369  /* Return NaN for +-Inf and NaN and raise exception.  */
370  else
371    mpfr_sub (x, x, x, GFC_RND_MODE);
372
373  RETURN (x);
374
375#else
376  ERROR_RETURN(cosd, KIND, x);
377#endif // ENABLE_COSD
378}
379#endif // COSD
380
381
382#ifdef TAND
383/* Compute tand(x) = tan(x * pi / 180).  */
384
385RETTYPE
386TAND (FTYPE x)
387{
388#ifdef ENABLE_TAND
389  if (ISFINITE (x))
390    {
391      FTYPE s, one;
392
393      /* tan(-x) = - tan(x).  */
394      mpfr_init (s);
395      mpfr_init_set_ui (one, 1, GFC_RND_MODE);
396      mpfr_copysign (s, one, x, GFC_RND_MODE);
397      mpfr_clear (one);
398
399#ifdef SIND_SMALL_LITERAL
400      /* tan(x) = x as x -> 0; but only for some precisions. */
401      FTYPE ax;
402      mpfr_init (ax);
403      mpfr_abs (ax, x, GFC_RND_MODE);
404      if (mpfr_cmp_ld (ax, SIND_SMALL_LITERAL) < 0)
405	{
406	  D2R (x);
407	  mpfr_clear (ax);
408	  return x;
409	}
410
411      mpfr_swap (x, ax);
412      mpfr_clear (ax);
413
414#else
415      mpfr_abs (x, x, GFC_RND_MODE);
416#endif /* SIND_SMALL_LITERAL */
417
418      /* Reduce angle to x in [0,360].  */
419      FTYPE period;
420      mpfr_init_set_ui (period, 360, GFC_RND_MODE);
421      mpfr_fmod (x, x, period, GFC_RND_MODE);
422      mpfr_clear (period);
423
424      /* Special cases with exact results. */
425      ITYPE n;
426      mpz_init (n);
427      if (mpfr_get_z (n, x, GFC_RND_MODE) == 0 && mpz_divisible_ui_p (n, 45))
428	{
429	  if (mpz_divisible_ui_p (n, 180))
430	    mpfr_set_zero (x, 0);
431
432	  /* Though mathematically NaN is more appropriate for tan(n*90),
433	     returning +/-Inf offers the advantage that 1/tan(n*90) returns 0,
434	     which is mathematically sound. In fact we rely on this behavior
435	     to implement COTAND(x) = 1 / TAND(x).
436	   */
437	  else if (mpz_divisible_ui_p (n, 90))
438	    mpfr_set_inf (x, mpz_cmp_ui (n, 90) == 0 ? 0 : 1);
439
440	  else
441	    {
442	      mpfr_set_ui (x, 1, GFC_RND_MODE);
443	      if (mpz_cmp_ui (n, 45) != 0 && mpz_cmp_ui (n, 225) != 0)
444		mpfr_neg (x, x, GFC_RND_MODE);
445	    }
446	}
447
448      else
449	{
450	  /* Fold [0,360] into the range [0,90], and compute TAN().  */
451	  if (mpfr_cmp_ui (x, 180) <= 0)
452	    {
453	      if (mpfr_cmp_ui (x, 90) > 0)
454		{
455		  mpfr_ui_sub (x, 180, x, GFC_RND_MODE);
456		  mpfr_neg (s, s, GFC_RND_MODE);
457		}
458	    }
459	  else
460	    {
461	      if (mpfr_cmp_ui (x, 270) <= 0)
462		{
463		  mpfr_sub_ui (x, x, 180, GFC_RND_MODE);
464		}
465	      else
466		{
467		  mpfr_ui_sub (x, 360, x, GFC_RND_MODE);
468		  mpfr_neg (s, s, GFC_RND_MODE);
469		}
470	    }
471
472	  D2R (x);
473	  mpfr_tan (x, x, GFC_RND_MODE);
474	}
475
476      mpfr_mul (x, x, s, GFC_RND_MODE);
477      mpz_clear (n);
478      mpfr_clear (s);
479    }
480
481  /* Return NaN for +-Inf and NaN and raise exception.  */
482  else
483    mpfr_sub (x, x, x, GFC_RND_MODE);
484
485  RETURN (x);
486
487#else
488  ERROR_RETURN(tand, KIND, x);
489#endif // ENABLE_TAND
490}
491#endif // TAND
492
493/* vim: set ft=c: */
494