Skip to content

Commit 1c1135b

Browse files
committed
feat: add the implementatio of atanpif16
1 parent c47042c commit 1c1135b

File tree

4 files changed

+196
-0
lines changed

4 files changed

+196
-0
lines changed

libc/src/math/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ add_math_entrypoint_object(atan2f128)
6767
add_math_entrypoint_object(atanh)
6868
add_math_entrypoint_object(atanhf)
6969

70+
add_math_entrypoint_object(atanpif16)
71+
7072
add_math_entrypoint_object(canonicalize)
7173
add_math_entrypoint_object(canonicalizef)
7274
add_math_entrypoint_object(canonicalizel)

libc/src/math/atanpif16.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
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_MATH_ATANPIF16_H
10+
#define LLVM_LIBC_SRC_MATH_ATANPIF16_H
11+
12+
#include "src/__support/macros/config.h"
13+
#include "src/__support/macros/properties/types.h"
14+
15+
namespace LIBC_NAMESPACE_DECL {
16+
17+
float16 atanpif16(float16 x);
18+
19+
} // namespace LIBC_NAMESPACE_DECL
20+
21+
#endif // LLVM_LIBC_SRC_MATH_ASINF16_H
22+

libc/src/math/generic/CMakeLists.txt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4029,6 +4029,23 @@ add_entrypoint_object(
40294029
libc.src.__support.macros.optimization
40304030
)
40314031

4032+
add_entrypoint_object(
4033+
atanpif16
4034+
SRCS
4035+
atanpif16.cpp
4036+
HDRS
4037+
../atanpif16.h
4038+
DEPENDS
4039+
libc.hdr.errno_macros
4040+
libc.hdr.fenv_macros
4041+
libc.src.__support.FPUtil.fenv_impl
4042+
libc.src.__support.FPUtil.fp_bits
4043+
libc.src.__support.FPUtil.polyeval
4044+
libc.src.__support.FPUtil.cast
4045+
libc.src.__support.FPUtil.multiply_add
4046+
libc.src.__support.FPUtil.sqrt
4047+
)
4048+
40324049
add_object_library(
40334050
inv_trigf_utils
40344051
HDRS
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
//===-- Half-precision atanpi function ------------------------------------===//
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+
#include "src/math/atanpif16.h"
10+
#include "hdr/errno_macros.h"
11+
#include "hdr/fenv_macros.h"
12+
#include "src/__support/FPUtil/FEnvImpl.h"
13+
#include "src/__support/FPUtil/FPBits.h"
14+
#include "src/__support/FPUtil/PolyEval.h"
15+
#include "src/__support/FPUtil/cast.h"
16+
#include "src/__support/FPUtil/multiply_add.h"
17+
#include "src/__support/FPUtil/sqrt.h"
18+
19+
namespace LIBC_NAMESPACE_DECL {
20+
21+
// Using Python's SymPy library, we can obtain the polynomial approximation of
22+
// arctan(x)/pi. The steps are as follows:
23+
// >>> from sympy import *
24+
// >>> import math
25+
// >>> x = symbols('x')
26+
// >>> print(series(atan(x)/math.pi, x, 0, 21))
27+
//
28+
// Output:
29+
// 0.318309886183791*x - 0.106103295394597*x**3 + 0.0636619772367581*x**5 -
30+
// 0.0454728408833987*x**7 + 0.0353677651315323*x**9 - 0.0289372623803446*x**11
31+
// + 0.0244853758602916*x**13 - 0.0212206590789194*x**15 +
32+
// 0.0187241109519877*x**17 - 0.01675315190441*x**19 + O(x**21)
33+
//
34+
// We will assign this 19-degree Taylor polynomial as g(x). This polynomial
35+
// approximation is accurate for arctan(x)/pi when |x| is in the range [0, 0.5].
36+
//
37+
//
38+
// To compute arctan(x) for all real x, we divide the domain into the following
39+
// cases:
40+
//
41+
// * Case 1: |x| <= 0.5
42+
// In this range, the direct polynomial approximation is used:
43+
// arctan(x)/pi = sign(x) * g(|x|)
44+
// or equivalently, arctan(x) = sign(x) * pi * g(|x|).
45+
//
46+
// * Case 2: 0.5 < |x| <= 1
47+
// We use the double-angle identity for the tangent function, specifically:
48+
// arctan(x) = 2 * arctan(x / (1 + sqrt(1 + x^2))).
49+
// Applying this, we have:
50+
// arctan(x)/pi = sign(x) * 2 * arctan(x')/pi,
51+
// where x' = |x| / (1 + sqrt(1 + x^2)).
52+
// Thus, arctan(x)/pi = sign(x) * 2 * g(x')
53+
//
54+
// When |x| is in (0.5, 1], the value of x' will always fall within the
55+
// interval [0.207, 0.414], which is within the accurate range of g(x).
56+
//
57+
// * Case 3: |x| > 1
58+
// For values of |x| greater than 1, we use the reciprocal transformation
59+
// identity:
60+
// arctan(x) = pi/2 - arctan(1/x) for x > 0.
61+
// For any x (real number), this generalizes to:
62+
// arctan(x)/pi = sign(x) * (1/2 - arctan(1/|x|)/pi).
63+
// Then, using g(x) for arctan(1/|x|)/pi:
64+
// arctan(x)/pi = sign(x) * (1/2 - g(1/|x|)).
65+
//
66+
// Note that if 1/|x| still falls outside the
67+
// g(x)'s primary range of accuracy (i.e., if 0.5 < 1/|x| <= 1), the rule
68+
// from Case 2 must be applied recursively to 1/|x|.
69+
70+
LLVM_LIBC_FUNCTION(float16, atanpif16, (float16 x)) {
71+
using FPBits = fputil::FPBits<float16>;
72+
73+
FPBits xbits(x);
74+
bool is_neg = xbits.is_neg();
75+
76+
auto signed_result = [is_neg](double r) -> float16 {
77+
return fputil::cast<float16>(is_neg ? -r : r);
78+
};
79+
80+
if (LIBC_UNLIKELY(xbits.is_inf_or_nan())) {
81+
if (xbits.is_nan()) {
82+
return x;
83+
}
84+
// atanpi(±∞) = ±0.5
85+
return signed_result(0.5);
86+
}
87+
88+
if (LIBC_UNLIKELY(xbits.is_zero())) {
89+
return x; //
90+
}
91+
92+
double x_abs = fputil::cast<double>(xbits.abs().get_val());
93+
94+
// Polynomial coefficients for atan(x)/pi Taylor series
95+
// Generated using SymPy: series(atan(x)/pi, x, 0, 21)
96+
constexpr double POLY_COEFFS[] = {
97+
0x1.45f306dc9c889p-2, // x^1: 1/pi
98+
-0x1.b2995e7b7b60bp-4, // x^3: -1/(3*pi)
99+
0x1.04c26be3b06ccp-4, // x^5: 1/(5*pi)
100+
-0x1.7483758e69c08p-5, // x^7: -1/(7*pi)
101+
0x1.21bb945252403p-5, // x^9: 1/(9*pi)
102+
-0x1.da1bace3cc68ep-6, // x^11: -1/(11*pi)
103+
0x1.912b1c2336cf2p-6, // x^13: 1/(13*pi)
104+
-0x1.5bade52f95e7p-6, // x^15: -1/(15*pi)
105+
0x1.32c69d0bde9e8p-6, // x^17: 1/(17*pi)
106+
-0x1.127bcfe232f8cp-6, // x^19: -1/(19*pi)
107+
};
108+
109+
// evaluate atan(x)/pi using polynomial approximation, valid for |x| <= 0.5
110+
constexpr auto atanpi_eval = [](double x) -> double {
111+
double xx = x * x;
112+
return x * fputil::polyeval(xx, POLY_COEFFS[0], POLY_COEFFS[1],
113+
POLY_COEFFS[2], POLY_COEFFS[3], POLY_COEFFS[4],
114+
POLY_COEFFS[5], POLY_COEFFS[6], POLY_COEFFS[7],
115+
POLY_COEFFS[8], POLY_COEFFS[9]);
116+
};
117+
118+
// Case 1: |x| <= 0.5 - Direct polynomial evaluation
119+
if (LIBC_LIKELY(x_abs <= 0.5)) {
120+
double result = atanpi_eval(x_abs);
121+
return signed_result(result);
122+
}
123+
124+
// Case 2: 0.5 < |x| <= 1 - Use double-angle reduction
125+
// atan(x) = 2 * atan(x / (1 + sqrt(1 + x^2)))
126+
// So atanpi(x) = 2 * atanpi(x') where x' = x / (1 + sqrt(1 + x^2))
127+
if (x_abs <= 1.0) {
128+
double x2 = x_abs * x_abs;
129+
double sqrt_term = fputil::sqrt<double>(1.0 + x2);
130+
double x_prime = x_abs / (1.0 + sqrt_term);
131+
double result = 2.0 * atanpi_eval(x_prime);
132+
return signed_result(result);
133+
}
134+
135+
// Case 3: |x| > 1 - Use reciprocal transformation
136+
// atan(x) = pi/2 - atan(1/x) for x > 0
137+
// So atanpi(x) = 1/2 - atanpi(1/x)
138+
double x_recip = 1.0 / x_abs;
139+
double result;
140+
141+
// if 1/|x| > 0.5, we need to apply Case 2 transformation to 1/|x|
142+
if (x_recip > 0.5) {
143+
double x_recip2 = x_recip * x_recip;
144+
double sqrt_term = fputil::sqrt<double>(1.0 + x_recip2);
145+
double x_prime = x_recip / (1.0 + sqrt_term);
146+
result = fputil::multiply_add(-2.0, atanpi_eval(x_prime), 0.5);
147+
} else {
148+
// direct evaluation since 1/|x| <= 0.5
149+
result = 0.5 - atanpi_eval(x_recip);
150+
}
151+
152+
return signed_result(result);
153+
}
154+
155+
} // namespace LIBC_NAMESPACE_DECL

0 commit comments

Comments
 (0)