Skip to content

Commit e03587c

Browse files
committed
[libc][math] Refactor atan implementation to header-only in src/__support/math folder.
1 parent 6a22580 commit e03587c

File tree

12 files changed

+286
-214
lines changed

12 files changed

+286
-214
lines changed

libc/shared/math.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "math/asinf16.h"
2323
#include "math/asinhf.h"
2424
#include "math/asinhf16.h"
25+
#include "math/atan.h"
2526
#include "math/erff.h"
2627
#include "math/exp.h"
2728
#include "math/exp10.h"

libc/shared/math/atan.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//===-- Shared atan function ------------------------------------*- 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_SHARED_MATH_ATAN_H
10+
#define LLVM_LIBC_SHARED_MATH_ATAN_H
11+
12+
#include "shared/libc_common.h"
13+
#include "src/__support/math/atan.h"
14+
15+
namespace LIBC_NAMESPACE_DECL {
16+
namespace shared {
17+
18+
using math::atan;
19+
20+
} // namespace shared
21+
} // namespace LIBC_NAMESPACE_DECL
22+
23+
#endif // LLVM_LIBC_SHARED_MATH_ATAN_H

libc/src/__support/math/CMakeLists.txt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,33 @@ DEPENDS
172172
libc.src.__support.macros.optimization
173173
)
174174

175+
add_header_library(
176+
atan_utils
177+
HDRS
178+
atan_utils.h
179+
DEPENDS
180+
libc.src.__support.integer_literals
181+
libc.src.__support.FPUtil.double_double
182+
libc.src.__support.FPUtil.dyadic_float
183+
libc.src.__support.FPUtil.multiply_add
184+
libc.src.__support.FPUtil.polyeval
185+
libc.src.__support.macros.optimization
186+
)
187+
188+
add_header_library(
189+
atan
190+
HDRS
191+
atan.h
192+
DEPENDS
193+
.atan_utils
194+
libc.src.__support.FPUtil.double_double
195+
libc.src.__support.FPUtil.fenv_impl
196+
libc.src.__support.FPUtil.fp_bits
197+
libc.src.__support.FPUtil.multiply_add
198+
libc.src.__support.FPUtil.nearest_integer
199+
libc.src.__support.macros.optimization
200+
)
201+
175202
add_header_library(
176203
asinf
177204
HDRS

libc/src/__support/math/atan.h

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
//===-- Implementation header for atan --------------------------*- 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_ATAN_H
10+
#define LLVM_LIBC_SRC___SUPPORT_MATH_ATAN_H
11+
12+
#include "atan_utils.h"
13+
#include "src/__support/FPUtil/FEnvImpl.h"
14+
#include "src/__support/FPUtil/FPBits.h"
15+
#include "src/__support/FPUtil/double_double.h"
16+
#include "src/__support/FPUtil/multiply_add.h"
17+
#include "src/__support/FPUtil/nearest_integer.h"
18+
#include "src/__support/macros/config.h"
19+
#include "src/__support/macros/optimization.h" // LIBC_UNLIKELY
20+
21+
namespace LIBC_NAMESPACE_DECL {
22+
23+
namespace math {
24+
25+
// To compute atan(x), we divided it into the following cases:
26+
// * |x| < 2^-26:
27+
// Since |x| > atan(|x|) > |x| - |x|^3/3, and |x|^3/3 < ulp(x)/2, we simply
28+
// return atan(x) = x - sign(x) * epsilon.
29+
// * 2^-26 <= |x| < 1:
30+
// We perform range reduction mod 2^-6 = 1/64 as follow:
31+
// Let k = 2^(-6) * round(|x| * 2^6), then
32+
// atan(x) = sign(x) * atan(|x|)
33+
// = sign(x) * (atan(k) + atan((|x| - k) / (1 + |x|*k)).
34+
// We store atan(k) in a look up table, and perform intermediate steps in
35+
// double-double.
36+
// * 1 < |x| < 2^53:
37+
// First we perform the transformation y = 1/|x|:
38+
// atan(x) = sign(x) * (pi/2 - atan(1/|x|))
39+
// = sign(x) * (pi/2 - atan(y)).
40+
// Then we compute atan(y) using range reduction mod 2^-6 = 1/64 as the
41+
// previous case:
42+
// Let k = 2^(-6) * round(y * 2^6), then
43+
// atan(y) = atan(k) + atan((y - k) / (1 + y*k))
44+
// = atan(k) + atan((1/|x| - k) / (1 + k/|x|)
45+
// = atan(k) + atan((1 - k*|x|) / (|x| + k)).
46+
// * |x| >= 2^53:
47+
// Using the reciprocal transformation:
48+
// atan(x) = sign(x) * (pi/2 - atan(1/|x|)).
49+
// We have that:
50+
// atan(1/|x|) <= 1/|x| <= 2^-53,
51+
// which is smaller than ulp(pi/2) / 2.
52+
// So we can return:
53+
// atan(x) = sign(x) * (pi/2 - epsilon)
54+
55+
LIBC_INLINE static constexpr double atan(double x) {
56+
57+
using namespace atan_internal;
58+
using FPBits = fputil::FPBits<double>;
59+
60+
constexpr double IS_NEG[2] = {1.0, -1.0};
61+
constexpr DoubleDouble PI_OVER_2 = {0x1.1a62633145c07p-54,
62+
0x1.921fb54442d18p0};
63+
constexpr DoubleDouble MPI_OVER_2 = {-0x1.1a62633145c07p-54,
64+
-0x1.921fb54442d18p0};
65+
66+
FPBits xbits(x);
67+
bool x_sign = xbits.is_neg();
68+
xbits = xbits.abs();
69+
uint64_t x_abs = xbits.uintval();
70+
int x_exp =
71+
static_cast<int>(x_abs >> FPBits::FRACTION_LEN) - FPBits::EXP_BIAS;
72+
73+
// |x| < 1.
74+
if (x_exp < 0) {
75+
if (LIBC_UNLIKELY(x_exp < -26)) {
76+
#ifdef LIBC_MATH_HAS_SKIP_ACCURATE_PASS
77+
return x;
78+
#else
79+
if (x == 0.0)
80+
return x;
81+
// |x| < 2^-26
82+
return fputil::multiply_add(-0x1.0p-54, x, x);
83+
#endif // LIBC_MATH_HAS_SKIP_ACCURATE_PASS
84+
}
85+
86+
double x_d = xbits.get_val();
87+
// k = 2^-6 * round(2^6 * |x|)
88+
double k = fputil::nearest_integer(0x1.0p6 * x_d);
89+
unsigned idx = static_cast<unsigned>(k);
90+
k *= 0x1.0p-6;
91+
92+
// numerator = |x| - k
93+
DoubleDouble num, den;
94+
num.lo = 0.0;
95+
num.hi = x_d - k;
96+
97+
// denominator = 1 - k * |x|
98+
den.hi = fputil::multiply_add(x_d, k, 1.0);
99+
DoubleDouble prod = fputil::exact_mult(x_d, k);
100+
// Using Dekker's 2SUM algorithm to compute the lower part.
101+
den.lo = ((1.0 - den.hi) + prod.hi) + prod.lo;
102+
103+
// x_r = (|x| - k) / (1 + k * |x|)
104+
DoubleDouble x_r = fputil::div(num, den);
105+
106+
// Approximating atan(x_r) using Taylor polynomial.
107+
DoubleDouble p = atan_eval(x_r);
108+
109+
// atan(x) = sign(x) * (atan(k) + atan(x_r))
110+
// = sign(x) * (atan(k) + atan( (|x| - k) / (1 + k * |x|) ))
111+
#ifdef LIBC_MATH_HAS_SKIP_ACCURATE_PASS
112+
return IS_NEG[x_sign] * (ATAN_I[idx].hi + (p.hi + (p.lo + ATAN_I[idx].lo)));
113+
#else
114+
115+
DoubleDouble c0 = fputil::exact_add(ATAN_I[idx].hi, p.hi);
116+
double c1 = c0.lo + (ATAN_I[idx].lo + p.lo);
117+
double r = IS_NEG[x_sign] * (c0.hi + c1);
118+
119+
return r;
120+
#endif // LIBC_MATH_HAS_SKIP_ACCURATE_PASS
121+
}
122+
123+
// |x| >= 2^53 or x is NaN.
124+
if (LIBC_UNLIKELY(x_exp >= 53)) {
125+
// x is nan
126+
if (xbits.is_nan()) {
127+
if (xbits.is_signaling_nan()) {
128+
fputil::raise_except_if_required(FE_INVALID);
129+
return FPBits::quiet_nan().get_val();
130+
}
131+
return x;
132+
}
133+
// |x| >= 2^53
134+
// atan(x) ~ sign(x) * pi/2.
135+
if (x_exp >= 53)
136+
#ifdef LIBC_MATH_HAS_SKIP_ACCURATE_PASS
137+
return IS_NEG[x_sign] * PI_OVER_2.hi;
138+
#else
139+
return fputil::multiply_add(IS_NEG[x_sign], PI_OVER_2.hi,
140+
IS_NEG[x_sign] * PI_OVER_2.lo);
141+
#endif // LIBC_MATH_HAS_SKIP_ACCURATE_PASS
142+
}
143+
144+
double x_d = xbits.get_val();
145+
double y = 1.0 / x_d;
146+
147+
// k = 2^-6 * round(2^6 / |x|)
148+
double k = fputil::nearest_integer(0x1.0p6 * y);
149+
unsigned idx = static_cast<unsigned>(k);
150+
k *= 0x1.0p-6;
151+
152+
// denominator = |x| + k
153+
DoubleDouble den = fputil::exact_add(x_d, k);
154+
// numerator = 1 - k * |x|
155+
DoubleDouble num;
156+
num.hi = fputil::multiply_add(-x_d, k, 1.0);
157+
DoubleDouble prod = fputil::exact_mult(x_d, k);
158+
// Using Dekker's 2SUM algorithm to compute the lower part.
159+
num.lo = ((1.0 - num.hi) - prod.hi) - prod.lo;
160+
161+
// x_r = (1/|x| - k) / (1 - k/|x|)
162+
// = (1 - k * |x|) / (|x| - k)
163+
DoubleDouble x_r = fputil::div(num, den);
164+
165+
// Approximating atan(x_r) using Taylor polynomial.
166+
DoubleDouble p = atan_eval(x_r);
167+
168+
// atan(x) = sign(x) * (pi/2 - atan(1/|x|))
169+
// = sign(x) * (pi/2 - atan(k) - atan(x_r))
170+
// = (-sign(x)) * (-pi/2 + atan(k) + atan((1 - k*|x|)/(|x| - k)))
171+
#ifdef LIBC_MATH_HAS_SKIP_ACCURATE_PASS
172+
double lo_part = p.lo + ATAN_I[idx].lo + MPI_OVER_2.lo;
173+
return IS_NEG[!x_sign] * (MPI_OVER_2.hi + ATAN_I[idx].hi + (p.hi + lo_part));
174+
#else
175+
DoubleDouble c0 = fputil::exact_add(MPI_OVER_2.hi, ATAN_I[idx].hi);
176+
DoubleDouble c1 = fputil::exact_add(c0.hi, p.hi);
177+
double c2 = c1.lo + (c0.lo + p.lo) + (ATAN_I[idx].lo + MPI_OVER_2.lo);
178+
179+
double r = IS_NEG[!x_sign] * (c1.hi + c2);
180+
181+
return r;
182+
#endif
183+
}
184+
185+
} // namespace math
186+
187+
} // namespace LIBC_NAMESPACE_DECL
188+
189+
#endif // LLVM_LIBC_SRC___SUPPORT_MATH_ATAN_H

libc/src/math/generic/atan_utils.h renamed to libc/src/__support/math/atan_utils.h

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
namespace LIBC_NAMESPACE_DECL {
2020

21-
namespace {
21+
namespace atan_internal {
2222

2323
using DoubleDouble = fputil::DoubleDouble;
2424
using Float128 = fputil::DyadicFloat<128>;
@@ -29,7 +29,7 @@ using Float128 = fputil::DyadicFloat<128>;
2929
// b = round(atan(i/64) - a, D, RN);
3030
// print("{", b, ",", a, "},");
3131
// };
32-
constexpr DoubleDouble ATAN_I[65] = {
32+
static constexpr DoubleDouble ATAN_I[65] = {
3333
{0.0, 0.0},
3434
{-0x1.220c39d4dff5p-61, 0x1.fff555bbb729bp-7},
3535
{-0x1.5ec431444912cp-60, 0x1.ffd55bba97625p-6},
@@ -110,7 +110,8 @@ constexpr DoubleDouble ATAN_I[65] = {
110110
// + x_lo * (1 - x_hi^2 + x_hi^4)
111111
// Since p.lo is ~ x^3/3, the relative error from rounding is bounded by:
112112
// |(atan(x) - P(x))/atan(x)| < ulp(x^2) <= 2^(-14-52) = 2^-66.
113-
[[maybe_unused]] DoubleDouble atan_eval(const DoubleDouble &x) {
113+
[[maybe_unused]] LIBC_INLINE static DoubleDouble
114+
atan_eval(const DoubleDouble &x) {
114115
DoubleDouble p;
115116
p.hi = x.hi;
116117
double x_hi_sq = x.hi * x.hi;
@@ -142,7 +143,7 @@ constexpr DoubleDouble ATAN_I[65] = {
142143
// b = 2^ll + a;
143144
// print("{Sign::POS, ", 2^(ll - 128), ",", b, "},");
144145
// };
145-
constexpr Float128 ATAN_I_F128[65] = {
146+
static constexpr Float128 ATAN_I_F128[65] = {
146147
{Sign::POS, 0, 0_u128},
147148
{Sign::POS, -134, 0xfffaaadd'db94d5bb'e78c5640'15f76048_u128},
148149
{Sign::POS, -133, 0xffeaaddd'4bb12542'779d776d'da8c6214_u128},
@@ -215,7 +216,7 @@ constexpr Float128 ATAN_I_F128[65] = {
215216
// [0, 2^-7]);
216217
// > dirtyinfnorm(atan(x) - P, [0, 2^-7]);
217218
// 0x1.26016ad97f323875760f869684c0898d7b7bb8bep-122
218-
constexpr Float128 ATAN_POLY_F128[] = {
219+
static constexpr Float128 ATAN_POLY_F128[] = {
219220
{Sign::NEG, -129, 0xaaaaaaaa'aaaaaaaa'aaaaaaa6'003c5d1d_u128},
220221
{Sign::POS, -130, 0xcccccccc'cccccccc'cca00232'8776b063_u128},
221222
{Sign::NEG, -130, 0x92492492'49249201'27f5268a'cb24aec0_u128},
@@ -225,7 +226,8 @@ constexpr Float128 ATAN_POLY_F128[] = {
225226
};
226227

227228
// Approximate atan for |x| <= 2^-7.
228-
[[maybe_unused]] Float128 atan_eval(const Float128 &x) {
229+
[[maybe_unused]] LIBC_INLINE static constexpr Float128
230+
atan_eval(const Float128 &x) {
229231
Float128 x_sq = fputil::quick_mul(x, x);
230232
Float128 x3 = fputil::quick_mul(x, x_sq);
231233
Float128 p = fputil::polyeval(x_sq, ATAN_POLY_F128[0], ATAN_POLY_F128[1],
@@ -234,7 +236,7 @@ constexpr Float128 ATAN_POLY_F128[] = {
234236
return fputil::multiply_add(x3, p, x);
235237
}
236238

237-
} // anonymous namespace
239+
} // namespace atan_internal
238240

239241
} // namespace LIBC_NAMESPACE_DECL
240242

libc/src/math/generic/CMakeLists.txt

Lines changed: 3 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4020,19 +4020,6 @@ add_entrypoint_object(
40204020
libc.src.errno.errno
40214021
)
40224022

4023-
add_header_library(
4024-
atan_utils
4025-
HDRS
4026-
atan_utils.h
4027-
DEPENDS
4028-
libc.src.__support.integer_literals
4029-
libc.src.__support.FPUtil.double_double
4030-
libc.src.__support.FPUtil.dyadic_float
4031-
libc.src.__support.FPUtil.multiply_add
4032-
libc.src.__support.FPUtil.polyeval
4033-
libc.src.__support.macros.optimization
4034-
)
4035-
40364023
add_entrypoint_object(
40374024
atanf
40384025
SRCS
@@ -4079,13 +4066,7 @@ add_entrypoint_object(
40794066
COMPILE_OPTIONS
40804067
-O3
40814068
DEPENDS
4082-
.atan_utils
4083-
libc.src.__support.FPUtil.double_double
4084-
libc.src.__support.FPUtil.fenv_impl
4085-
libc.src.__support.FPUtil.fp_bits
4086-
libc.src.__support.FPUtil.multiply_add
4087-
libc.src.__support.FPUtil.nearest_integer
4088-
libc.src.__support.macros.optimization
4069+
libc.src.__support.math.atan
40894070
)
40904071

40914072
add_entrypoint_object(
@@ -4115,7 +4096,7 @@ add_entrypoint_object(
41154096
HDRS
41164097
../atan2.h
41174098
DEPENDS
4118-
.atan_utils
4099+
libc.src.__support.math.atan_utils
41194100
libc.src.__support.FPUtil.double_double
41204101
libc.src.__support.FPUtil.fenv_impl
41214102
libc.src.__support.FPUtil.fp_bits
@@ -4141,7 +4122,7 @@ add_entrypoint_object(
41414122
HDRS
41424123
../atan2f128.h
41434124
DEPENDS
4144-
.atan_utils
4125+
libc.src.__support.math.atan_utils
41454126
libc.src.__support.integer_literals
41464127
libc.src.__support.uint128
41474128
libc.src.__support.FPUtil.dyadic_float

0 commit comments

Comments
 (0)