1//===-- Implementation header for atanpif16 ---------------------*- C++ -*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#ifndef LLVM_LIBC_SRC___SUPPORT_MATH_ATANPIF16_H
10#define LLVM_LIBC_SRC___SUPPORT_MATH_ATANPIF16_H
11
12#include "include/llvm-libc-macros/float16-macros.h"
13
14#ifdef LIBC_TYPES_HAS_FLOAT16
15
16#include "hdr/fenv_macros.h"
17#include "src/__support/FPUtil/FEnvImpl.h"
18#include "src/__support/FPUtil/FPBits.h"
19#include "src/__support/FPUtil/PolyEval.h"
20#include "src/__support/FPUtil/cast.h"
21#include "src/__support/FPUtil/multiply_add.h"
22#include "src/__support/FPUtil/sqrt.h"
23#include "src/__support/macros/optimization.h"
24
25namespace LIBC_NAMESPACE_DECL {
26namespace math {
27
28// Using Python's SymPy library, we can obtain the polynomial approximation of
29// arctan(x)/pi. The steps are as follows:
30// >>> from sympy import *
31// >>> import math
32// >>> x = symbols('x')
33// >>> print(series(atan(x)/math.pi, x, 0, 17))
34//
35// Output:
36// 0.318309886183791*x - 0.106103295394597*x**3 + 0.0636619772367581*x**5 -
37// 0.0454728408833987*x**7 + 0.0353677651315323*x**9 - 0.0289372623803446*x**11
38// + 0.0244853758602916*x**13 - 0.0212206590789194*x**15 + O(x**17)
39//
40// We will assign this degree-15 Taylor polynomial as g(x). This polynomial
41// approximation is accurate for arctan(x)/pi when |x| is in the range [0, 0.5].
42//
43//
44// To compute arctan(x) for all real x, we divide the domain into the following
45// cases:
46//
47// * Case 1: |x| <= 0.5
48// In this range, the direct polynomial approximation is used:
49// arctan(x)/pi = sign(x) * g(|x|)
50// or equivalently, arctan(x) = sign(x) * pi * g(|x|).
51//
52// * Case 2: 0.5 < |x| <= 1
53// We use the double-angle identity for the tangent function, specifically:
54// arctan(x) = 2 * arctan(x / (1 + sqrt(1 + x^2))).
55// Applying this, we have:
56// arctan(x)/pi = sign(x) * 2 * arctan(x')/pi,
57// where x' = |x| / (1 + sqrt(1 + x^2)).
58// Thus, arctan(x)/pi = sign(x) * 2 * g(x')
59//
60// When |x| is in (0.5, 1], the value of x' will always fall within the
61// interval [0.207, 0.414], which is within the accurate range of g(x).
62//
63// * Case 3: |x| > 1
64// For values of |x| greater than 1, we use the reciprocal transformation
65// identity:
66// arctan(x) = pi/2 - arctan(1/x) for x > 0.
67// For any x (real number), this generalizes to:
68// arctan(x)/pi = sign(x) * (1/2 - arctan(1/|x|)/pi).
69// Then, using g(x) for arctan(1/|x|)/pi:
70// arctan(x)/pi = sign(x) * (1/2 - g(1/|x|)).
71//
72// Note that if 1/|x| still falls outside the
73// g(x)'s primary range of accuracy (i.e., if 0.5 < 1/|x| <= 1), the rule
74// from Case 2 must be applied recursively to 1/|x|.
75
76LIBC_INLINE float16 atanpif16(float16 x) {
77 using FPBits = fputil::FPBits<float16>;
78
79 FPBits xbits(x);
80 bool is_neg = xbits.is_neg();
81
82 auto signed_result = [is_neg](double r) -> float16 {
83 return fputil::cast<float16>(x: is_neg ? -r : r);
84 };
85
86 if (LIBC_UNLIKELY(xbits.is_inf_or_nan())) {
87 if (xbits.is_nan()) {
88 if (xbits.is_signaling_nan()) {
89 fputil::raise_except_if_required(FE_INVALID);
90 return FPBits::quiet_nan().get_val();
91 }
92 return x;
93 }
94 // atanpi(+-inf) = +-0.5
95 return signed_result(0.5);
96 }
97
98 if (LIBC_UNLIKELY(xbits.is_zero()))
99 return x;
100
101 double x_abs = fputil::cast<double>(x: xbits.abs().get_val());
102
103 if (LIBC_UNLIKELY(x_abs == 1.0))
104 return signed_result(0.25);
105
106 // evaluate atan(x)/pi using polynomial approximation, valid for |x| <= 0.5
107 constexpr auto atanpi_eval = [](double x) -> double {
108 // polynomial coefficients for atan(x)/pi taylor series
109 // generated using sympy: series(atan(x)/pi, x, 0, 17)
110 constexpr static double POLY_COEFFS[] = {
111 0x1.45f306dc9c889p-2, // x^1: 1/pi
112 -0x1.b2995e7b7b60bp-4, // x^3: -1/(3*pi)
113 0x1.04c26be3b06ccp-4, // x^5: 1/(5*pi)
114 -0x1.7483758e69c08p-5, // x^7: -1/(7*pi)
115 0x1.21bb945252403p-5, // x^9: 1/(9*pi)
116 -0x1.da1bace3cc68ep-6, // x^11: -1/(11*pi)
117 0x1.912b1c2336cf2p-6, // x^13: 1/(13*pi)
118 -0x1.5bade52f95e7p-6, // x^15: -1/(15*pi)
119 };
120 double x_sq = x * x;
121 return x * fputil::polyeval(x: x_sq, a0: POLY_COEFFS[0], a: POLY_COEFFS[1],
122 a: POLY_COEFFS[2], a: POLY_COEFFS[3], a: POLY_COEFFS[4],
123 a: POLY_COEFFS[5], a: POLY_COEFFS[6], a: POLY_COEFFS[7]);
124 };
125
126 // case 1: |x| <= 0.5 - direct polynomial evaluation
127 if (LIBC_LIKELY(x_abs <= 0.5)) {
128 double result = atanpi_eval(x_abs);
129 return signed_result(result);
130 }
131
132 // case 2: 0.5 < |x| <= 1 - use double-angle reduction
133 // atan(x) = 2 * atan(x / (1 + sqrt(1 + x^2)))
134 // so atanpi(x) = 2 * atanpi(x') where x' = x / (1 + sqrt(1 + x^2))
135 if (x_abs <= 1.0) {
136 double x_abs_sq = x_abs * x_abs;
137 double sqrt_term = fputil::sqrt<double>(x: 1.0 + x_abs_sq);
138 double x_prime = x_abs / (1.0 + sqrt_term);
139 double result = 2.0 * atanpi_eval(x_prime);
140 return signed_result(result);
141 }
142
143 // case 3: |x| > 1 - use reciprocal transformation
144 // atan(x) = pi/2 - atan(1/x) for x > 0
145 // so atanpi(x) = 1/2 - atanpi(1/x)
146 double x_recip = 1.0 / x_abs;
147 double result = 0.0;
148
149 // if 1/|x| > 0.5, we need to apply Case 2 transformation to 1/|x|
150 if (x_recip > 0.5) {
151 double x_recip_sq = x_recip * x_recip;
152 double sqrt_term = fputil::sqrt<double>(x: 1.0 + x_recip_sq);
153 double x_prime = x_recip / (1.0 + sqrt_term);
154 result = fputil::multiply_add(x: -2.0, y: atanpi_eval(x_prime), z: 0.5);
155 } else {
156 // direct evaluation since 1/|x| <= 0.5
157 result = 0.5 - atanpi_eval(x_recip);
158 }
159
160 return signed_result(result);
161}
162
163} // namespace math
164} // namespace LIBC_NAMESPACE_DECL
165
166#endif // LIBC_TYPES_HAS_FLOAT16
167
168#endif // LLVM_LIBC_SRC___SUPPORT_MATH_ATANPIF16_H
169