Skip to content

Commit aa3f930

Browse files
authored
[libc][math] Add float-only implementation for atanf. (#167004)
Algorithm: ``` 1) atan(x) = sign(x) * atan(|x|) 2) If |x| > 1 + 1/32, atan(|x|) = pi/2 - atan(1/|x|) 3) For 1/16 < |x| < 1 + 1/32, we find k such that: | |x| - k/16 | <= 1/32. Let y = |x| - k/16, then using the angle summation formula, we have: atan(|x|) = atan(k/16) + atan( (|x| - k/16) / (1 + |x| * k/16) ) = atan(k/16) + atan( y / (1 + (y + k/16) * k/16 ) = atan(k/16) + atan( y / ((1 + k^2/256) + y * k/16) ) 4) Let u = y / (1 + k^2/256), then we can rewritten the above as: atan(|x|) = atan(k/16) + atan( u / (1 + u * k/16) ) ~ atan(k/16) + (u - k/16 * u^2 + (k^2/256 - 1/3) * u^3 + + (k/16 - (k/16)^3) * u^4) + O(u^5) ``` With all the computations are done in single precision (float), the total of approximation errors and rounding errors is bounded by 4 ULPs.
1 parent a74bfc0 commit aa3f930

File tree

6 files changed

+245
-3
lines changed

6 files changed

+245
-3
lines changed

libc/src/__support/math/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@ add_header_library(
248248
add_header_library(
249249
atanf
250250
HDRS
251+
atanf_float.h
251252
atanf.h
252253
DEPENDS
253254
.inv_trigf_utils

libc/src/__support/math/atanf.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,14 @@
1818
#include "src/__support/macros/config.h"
1919
#include "src/__support/macros/optimization.h" // LIBC_UNLIKELY
2020

21+
#if defined(LIBC_MATH_HAS_SKIP_ACCURATE_PASS) && \
22+
defined(LIBC_MATH_HAS_INTERMEDIATE_COMP_IN_FLOAT)
23+
24+
// We use float-float implementation to reduce size.
25+
#include "atanf_float.h"
26+
27+
#else
28+
2129
namespace LIBC_NAMESPACE_DECL {
2230

2331
namespace math {
@@ -126,4 +134,6 @@ LIBC_INLINE static constexpr float atanf(float x) {
126134

127135
} // namespace LIBC_NAMESPACE_DECL
128136

137+
#endif // LIBC_MATH_HAS_SKIP_ACCURATE_PASS
138+
129139
#endif // LLVM_LIBC_SRC___SUPPORT_MATH_ATANF_H
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
//===-- Single-precision atanf float 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+
#ifndef LIBC_SRC___SUPPORT_MATH_ATANF_FLOAT_H
10+
#define LIBC_SRC___SUPPORT_MATH_ATANF_FLOAT_H
11+
12+
#include "src/__support/FPUtil/FEnvImpl.h"
13+
#include "src/__support/FPUtil/FPBits.h"
14+
#include "src/__support/FPUtil/double_double.h"
15+
#include "src/__support/FPUtil/multiply_add.h"
16+
#include "src/__support/FPUtil/nearest_integer.h"
17+
#include "src/__support/macros/config.h"
18+
#include "src/__support/macros/optimization.h" // LIBC_UNLIKELY
19+
20+
namespace LIBC_NAMESPACE_DECL {
21+
22+
namespace math {
23+
24+
namespace atanf_internal {
25+
26+
using fputil::FloatFloat;
27+
// atan(i/64) with i = 0..16, generated by Sollya with:
28+
// > for i from 0 to 16 do {
29+
// a = round(atan(i/16), SG, RN);
30+
// b = round(atan(i/16) - a, SG, RN);
31+
// print("{", b, ",", a, "},");
32+
// };
33+
static constexpr FloatFloat ATAN_I[17] = {
34+
{0.0f, 0.0f},
35+
{-0x1.1a6042p-30f, 0x1.ff55bcp-5f},
36+
{-0x1.54f424p-30f, 0x1.fd5baap-4f},
37+
{0x1.79cb6p-28f, 0x1.7b97b4p-3f},
38+
{-0x1.b4dfc8p-29f, 0x1.f5b76p-3f},
39+
{-0x1.1f0286p-27f, 0x1.362774p-2f},
40+
{0x1.e4defp-30f, 0x1.6f6194p-2f},
41+
{0x1.e611fep-29f, 0x1.a64eecp-2f},
42+
{0x1.586ed4p-28f, 0x1.dac67p-2f},
43+
{-0x1.6499e6p-26f, 0x1.0657eap-1f},
44+
{0x1.7bdfd6p-26f, 0x1.1e00bap-1f},
45+
{-0x1.98e422p-28f, 0x1.345f02p-1f},
46+
{0x1.934f7p-28f, 0x1.4978fap-1f},
47+
{0x1.c5a6c6p-27f, 0x1.5d5898p-1f},
48+
{0x1.5e118cp-27f, 0x1.700a7cp-1f},
49+
{-0x1.1d4eb6p-26f, 0x1.819d0cp-1f},
50+
{-0x1.777a5cp-26f, 0x1.921fb6p-1f},
51+
};
52+
53+
// 1 / (1 + (i/16)^2) with i = 0..16, generated by Sollya with:
54+
// > for i from 0 to 16 do {
55+
// a = round(1 / (1 + (i/16)^2), SG, RN);
56+
// print(a, ",");
57+
// };
58+
static constexpr float ATANF_REDUCED_ARG[17] = {
59+
0x1.0p0f, 0x1.fe01fep-1f, 0x1.f81f82p-1f, 0x1.ee9c8p-1f,
60+
0x1.e1e1e2p-1f, 0x1.d272cap-1f, 0x1.c0e07p-1f, 0x1.adbe88p-1f,
61+
0x1.99999ap-1f, 0x1.84f00cp-1f, 0x1.702e06p-1f, 0x1.5babccp-1f,
62+
0x1.47ae14p-1f, 0x1.34679ap-1f, 0x1.21fb78p-1f, 0x1.107fbcp-1f,
63+
0x1p-1f,
64+
};
65+
66+
// Approximating atan( u / (1 + u * k/16) )
67+
// atan( u / (1 + u * k/16) ) / u ~ 1 - k/16 * u + (k^2/256 - 1/3) * u^2 +
68+
// + (k/16 - (k/16)^3) * u^3 + O(u^4)
69+
LIBC_INLINE static float atanf_eval(float u, float k_over_16) {
70+
// (k/16)^2
71+
float c2 = k_over_16 * k_over_16;
72+
// -(k/16)^3
73+
float c3 = fputil::multiply_add(-k_over_16, c2, k_over_16);
74+
float u2 = u * u;
75+
// (k^2/256 - 1/3) + u * (k/16 - (k/16)^3)
76+
float a0 = fputil::multiply_add(c3, u, c2 - 0x1.555556p-2f);
77+
// -k/16 + u*(k^2/256 - 1/3) + u^2 * (k/16 - (k/16)^3)
78+
float a1 = fputil::multiply_add(u, a0, -k_over_16);
79+
// u - u^2 * k/16 + u^3 * ((k^2/256 - 1/3) + u^4 * (k/16 - (k/16)^3))
80+
return fputil::multiply_add(u2, a1, u);
81+
}
82+
83+
} // namespace atanf_internal
84+
85+
// There are several range reduction steps we can take for atan2(y, x) as
86+
// follow:
87+
88+
LIBC_INLINE static float atanf(float x) {
89+
using namespace atanf_internal;
90+
using FPBits = typename fputil::FPBits<float>;
91+
using FPBits = typename fputil::FPBits<float>;
92+
93+
constexpr float SIGN[2] = {1.0f, -1.0f};
94+
constexpr FloatFloat PI_OVER_2 = {-0x1.777a5cp-25f, 0x1.921fb6p0f};
95+
96+
FPBits x_bits(x);
97+
Sign s = x_bits.sign();
98+
float sign = SIGN[s.is_neg()];
99+
uint32_t x_abs = x_bits.uintval() & 0x7fff'ffffU;
100+
101+
// x is inf or nan, |x| <= 2^-11 or |x|= > 2^11.
102+
if (LIBC_UNLIKELY(x_abs <= 0x3a00'0000U || x_abs >= 0x4500'0000U)) {
103+
if (LIBC_UNLIKELY(x_bits.is_inf()))
104+
return sign * PI_OVER_2.hi;
105+
// atan(NaN) = NaN
106+
if (LIBC_UNLIKELY(x_bits.is_nan())) {
107+
if (x_bits.is_signaling_nan()) {
108+
fputil::raise_except_if_required(FE_INVALID);
109+
return FPBits::quiet_nan().get_val();
110+
}
111+
112+
return x;
113+
}
114+
// |x| >= 2^11:
115+
// atan(x) = sign(x) * pi/2 - atan(1/x)
116+
// ~ sign(x) * pi/2 - 1/x
117+
if (LIBC_UNLIKELY(x_abs >= 0x4200'0000))
118+
return fputil::multiply_add(sign, PI_OVER_2.hi, -1.0f / x);
119+
// x <= 2^-11:
120+
// atan(x) ~ x
121+
return x;
122+
}
123+
124+
// Range reduction steps:
125+
// 1) atan(x) = sign(x) * atan(|x|)
126+
// 2) If |x| > 1, atan(|x|) = pi/2 - atan(1/|x|)
127+
// 3) For 1/16 < |x| < 1 + 1/32, we find k such that: | |x| - k/16 | <= 1/32.
128+
// Let y = |x| - k/16, then using the angle summation formula, we have:
129+
// atan(|x|) = atan(k/16) + atan( (|x| - k/16) / (1 + |x| * k/16) )
130+
// = atan(k/16) + atan( y / (1 + (y + k/16) * k/16 )
131+
// = atan(k/16) + atan( y / ((1 + k^2/256) + y * k/16) )
132+
// 4) Let u = y / (1 + k^2/256), then we can rewritten the above as:
133+
// atan(|x|) = atan(k/16) + atan( u / (1 + u * k/16) )
134+
// ~ atan(k/16) + (u - k/16 * u^2 + (k^2/256 - 1/3) * u^3 +
135+
// + (k/16 - (k/16)^3) * u^4) + O(u^5)
136+
float x_a = cpp::bit_cast<float>(x_abs);
137+
// |x| > 1 + 1/32, we need to invert x, so we will perform the division in
138+
// float-float.
139+
if (x_abs > 0x3f84'0000U)
140+
x_a = 1.0f / x_a;
141+
// Perform range reduction.
142+
// k = nearestint(x * 16)
143+
float k_f = fputil::nearest_integer(x_a * 0x1.0p4f);
144+
unsigned idx = static_cast<unsigned>(k_f);
145+
float k_over_16 = k_f * 0x1.0p-4f;
146+
float y = x_a - k_over_16;
147+
// u = (x - k/16) / (1 + (k/16)^2)
148+
float u = y * ATANF_REDUCED_ARG[idx];
149+
150+
// atan(x) = sign(x) * atan(|x|)
151+
// = sign(x) * (atan(k/16) + atan(|))
152+
// p ~ atan(u)
153+
float p = atanf_eval(u, k_over_16);
154+
// |x| > 1 + 1/32: q ~ (pi/2 - atan(1/|x|))
155+
// |x| <= 1 + 1/32: q ~ atan(|x|)
156+
float q = (p + ATAN_I[idx].lo) + ATAN_I[idx].hi;
157+
if (x_abs > 0x3f84'0000U)
158+
q = PI_OVER_2.hi + (PI_OVER_2.lo - q);
159+
// |x| > 1 + 1/32: sign(x) * (pi/2 - atan(1/|x|))
160+
// |x| <= 1 + 1/32: sign(x) * atan(|x|)
161+
return sign * q;
162+
}
163+
164+
} // namespace math
165+
166+
} // namespace LIBC_NAMESPACE_DECL
167+
168+
#endif // LIBC_SRC___SUPPORT_MATH_ATANF_FLOAT_H

libc/test/src/math/atanf_test.cpp

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,18 @@
99
#include "hdr/math_macros.h"
1010
#include "hdr/stdint_proxy.h"
1111
#include "src/__support/FPUtil/FPBits.h"
12+
#include "src/__support/macros/optimization.h"
1213
#include "src/math/atanf.h"
1314
#include "test/UnitTest/FPMatcher.h"
1415
#include "test/UnitTest/Test.h"
1516
#include "utils/MPFRWrapper/MPFRUtils.h"
1617

18+
#ifdef LIBC_MATH_HAS_SKIP_ACCURATE_PASS
19+
#define TOLERANCE 4
20+
#else
21+
#define TOLERANCE 0
22+
#endif // LIBC_MATH_HAS_SKIP_ACCURATE_PASS
23+
1724
using LlvmLibcAtanfTest = LIBC_NAMESPACE::testing::FPTest<float>;
1825

1926
namespace mpfr = LIBC_NAMESPACE::testing::mpfr;
@@ -49,9 +56,9 @@ TEST_F(LlvmLibcAtanfTest, InFloatRange) {
4956
for (uint32_t i = 0, v = 0; i <= COUNT; ++i, v += STEP) {
5057
float x = FPBits(v).get_val();
5158
EXPECT_MPFR_MATCH_ALL_ROUNDING(mpfr::Operation::Atan, x,
52-
LIBC_NAMESPACE::atanf(x), 0.5);
59+
LIBC_NAMESPACE::atanf(x), TOLERANCE + 0.5);
5360
EXPECT_MPFR_MATCH_ALL_ROUNDING(mpfr::Operation::Atan, -x,
54-
LIBC_NAMESPACE::atanf(-x), 0.5);
61+
LIBC_NAMESPACE::atanf(-x), TOLERANCE + 0.5);
5562
}
5663
}
5764

@@ -72,6 +79,6 @@ TEST_F(LlvmLibcAtanfTest, SpecialValues) {
7279
for (uint32_t v : val_arr) {
7380
float x = FPBits(v).get_val();
7481
EXPECT_MPFR_MATCH_ALL_ROUNDING(mpfr::Operation::Atan, x,
75-
LIBC_NAMESPACE::atanf(x), 0.5);
82+
LIBC_NAMESPACE::atanf(x), TOLERANCE + 0.5);
7683
}
7784
}

libc/test/src/math/exhaustive/CMakeLists.txt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,21 @@ add_fp_unittest(
550550
-lpthread
551551
)
552552

553+
add_fp_unittest(
554+
atanf_float_test
555+
NO_RUN_POSTBUILD
556+
NEED_MPFR
557+
SUITE
558+
libc_math_exhaustive_tests
559+
SRCS
560+
atanf_float_test.cpp
561+
LINK_LIBRARIES
562+
-lpthread
563+
DEPENDS
564+
.exhaustive_test
565+
libc.src.__support.math.atanf
566+
)
567+
553568
add_fp_unittest(
554569
asinf_test
555570
NO_RUN_POSTBUILD
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//===-- Exhaustive test for atanf - float-only ----------------------------===//
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 "exhaustive_test.h"
10+
#include "src/__support/math/atanf_float.h"
11+
#include "utils/MPFRWrapper/MPFRUtils.h"
12+
13+
namespace mpfr = LIBC_NAMESPACE::testing::mpfr;
14+
15+
float atanf_fast(float x) { return LIBC_NAMESPACE::math::atanf(x); }
16+
17+
using LlvmLibcAtanfFloatExhaustiveTest =
18+
LlvmLibcUnaryOpExhaustiveMathTest<float, mpfr::Operation::Atan, atanf_fast,
19+
4>;
20+
21+
// Range: [0, Inf];
22+
static constexpr uint32_t POS_START = 0x0000'0000U;
23+
static constexpr uint32_t POS_STOP = 0x7f80'0000U;
24+
25+
TEST_F(LlvmLibcAtanfFloatExhaustiveTest, PostiveRange) {
26+
std::cout << "-- Testing for FE_TONEAREST in range [0x" << std::hex
27+
<< POS_START << ", 0x" << POS_STOP << ") --" << std::dec
28+
<< std::endl;
29+
test_full_range(mpfr::RoundingMode::Nearest, POS_START, POS_STOP);
30+
}
31+
32+
// Range: [-Inf, 0];
33+
static constexpr uint32_t NEG_START = 0x8000'0000U;
34+
static constexpr uint32_t NEG_STOP = 0xff80'0000U;
35+
36+
TEST_F(LlvmLibcAtanfFloatExhaustiveTest, NegativeRange) {
37+
std::cout << "-- Testing for FE_TONEAREST in range [0x" << std::hex
38+
<< NEG_START << ", 0x" << NEG_STOP << ") --" << std::dec
39+
<< std::endl;
40+
test_full_range(mpfr::RoundingMode::Nearest, NEG_START, NEG_STOP);
41+
}

0 commit comments

Comments
 (0)