Skip to content

[libc][math][c23] Implement C23 math function atanpif16 #150400

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions libc/config/linux/x86_64/entrypoints.txt
Original file line number Diff line number Diff line change
Expand Up @@ -705,6 +705,7 @@ if(LIBC_TYPES_HAS_FLOAT16)
libc.src.math.asinhf16
libc.src.math.atanf16
libc.src.math.atanhf16
libc.src.math.atanpif16
libc.src.math.canonicalizef16
libc.src.math.ceilf16
libc.src.math.copysignf16
Expand Down
2 changes: 1 addition & 1 deletion libc/docs/headers/math/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ Higher Math Functions
+-----------+------------------+-----------------+------------------------+----------------------+------------------------+------------------------+----------------------------+
| atanh | |check| | | | |check| | | 7.12.5.3 | F.10.2.3 |
+-----------+------------------+-----------------+------------------------+----------------------+------------------------+------------------------+----------------------------+
| atanpi | | | | | | 7.12.4.10 | F.10.1.10 |
| atanpi | | | | |check| | | 7.12.4.10 | F.10.1.10 |
+-----------+------------------+-----------------+------------------------+----------------------+------------------------+------------------------+----------------------------+
| cbrt | |check| | |check| | | | | 7.12.7.1 | F.10.4.1 |
+-----------+------------------+-----------------+------------------------+----------------------+------------------------+------------------------+----------------------------+
Expand Down
7 changes: 7 additions & 0 deletions libc/include/math.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,13 @@ functions:
arguments:
- type: float
- name: atanhf16
standatds:
- stdc
return_type: _Float16
arguments:
- type: _Float16
guard: LIBC_TYPES_HAS_FLOAT16
- name: atanpif16
standards:
- stdc
return_type: _Float16
Expand Down
2 changes: 2 additions & 0 deletions libc/src/math/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ add_math_entrypoint_object(atanh)
add_math_entrypoint_object(atanhf)
add_math_entrypoint_object(atanhf16)

add_math_entrypoint_object(atanpif16)

add_math_entrypoint_object(canonicalize)
add_math_entrypoint_object(canonicalizef)
add_math_entrypoint_object(canonicalizel)
Expand Down
21 changes: 21 additions & 0 deletions libc/src/math/atanpif16.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//===-- Implementation header for atanpif16 ---------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception.
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_LIBC_SRC_MATH_ATANPIF16_H
#define LLVM_LIBC_SRC_MATH_ATANPIF16_H

#include "src/__support/macros/config.h"
#include "src/__support/macros/properties/types.h"

namespace LIBC_NAMESPACE_DECL {

float16 atanpif16(float16 x);

} // namespace LIBC_NAMESPACE_DECL

#endif // LLVM_LIBC_SRC_MATH_ASINF16_H
19 changes: 19 additions & 0 deletions libc/src/math/generic/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3951,6 +3951,25 @@ add_entrypoint_object(
libc.src.__support.macros.properties.types
)

add_entrypoint_object(
atanpif16
SRCS
atanpif16.cpp
HDRS
../atanpif16.h
DEPENDS
libc.hdr.errno_macros
libc.hdr.fenv_macros
libc.src.__support.FPUtil.cast
libc.src.__support.FPUtil.fenv_impl
libc.src.__support.FPUtil.fp_bits
libc.src.__support.FPUtil.multiply_add
libc.src.__support.FPUtil.polyeval
libc.src.__support.FPUtil.sqrt
libc.src.__support.macros.optimization
libc.src.__support.macros.properties.types
)

add_entrypoint_object(
asinf
SRCS
Expand Down
158 changes: 158 additions & 0 deletions libc/src/math/generic/atanpif16.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
//===-- Half-precision atanpi function ------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "src/math/atanpif16.h"
#include "hdr/errno_macros.h"
#include "hdr/fenv_macros.h"
#include "src/__support/FPUtil/FEnvImpl.h"
#include "src/__support/FPUtil/FPBits.h"
#include "src/__support/FPUtil/PolyEval.h"
#include "src/__support/FPUtil/cast.h"
#include "src/__support/FPUtil/multiply_add.h"
#include "src/__support/FPUtil/sqrt.h"
#include "src/__support/macros/optimization.h"

namespace LIBC_NAMESPACE_DECL {

// Using Python's SymPy library, we can obtain the polynomial approximation of
// arctan(x)/pi. The steps are as follows:
// >>> from sympy import *
// >>> import math
// >>> x = symbols('x')
// >>> print(series(atan(x)/math.pi, x, 0, 17))
//
// Output:
// 0.318309886183791*x - 0.106103295394597*x**3 + 0.0636619772367581*x**5 -
// 0.0454728408833987*x**7 + 0.0353677651315323*x**9 - 0.0289372623803446*x**11
// + 0.0244853758602916*x**13 - 0.0212206590789194*x**15 + O(x**17)
//
// We will assign this 19-degree Taylor polynomial as g(x). This polynomial
// approximation is accurate for arctan(x)/pi when |x| is in the range [0, 0.5].
//
//
// To compute arctan(x) for all real x, we divide the domain into the following
// cases:
//
// * Case 1: |x| <= 0.5
// In this range, the direct polynomial approximation is used:
// arctan(x)/pi = sign(x) * g(|x|)
// or equivalently, arctan(x) = sign(x) * pi * g(|x|).
//
// * Case 2: 0.5 < |x| <= 1
// We use the double-angle identity for the tangent function, specifically:
// arctan(x) = 2 * arctan(x / (1 + sqrt(1 + x^2))).
// Applying this, we have:
// arctan(x)/pi = sign(x) * 2 * arctan(x')/pi,
// where x' = |x| / (1 + sqrt(1 + x^2)).
// Thus, arctan(x)/pi = sign(x) * 2 * g(x')
//
// When |x| is in (0.5, 1], the value of x' will always fall within the
// interval [0.207, 0.414], which is within the accurate range of g(x).
//
// * Case 3: |x| > 1
// For values of |x| greater than 1, we use the reciprocal transformation
// identity:
// arctan(x) = pi/2 - arctan(1/x) for x > 0.
// For any x (real number), this generalizes to:
// arctan(x)/pi = sign(x) * (1/2 - arctan(1/|x|)/pi).
// Then, using g(x) for arctan(1/|x|)/pi:
// arctan(x)/pi = sign(x) * (1/2 - g(1/|x|)).
//
// Note that if 1/|x| still falls outside the
// g(x)'s primary range of accuracy (i.e., if 0.5 < 1/|x| <= 1), the rule
// from Case 2 must be applied recursively to 1/|x|.

LLVM_LIBC_FUNCTION(float16, atanpif16, (float16 x)) {
using FPBits = fputil::FPBits<float16>;

FPBits xbits(x);
bool is_neg = xbits.is_neg();

auto signed_result = [is_neg](double r) -> float16 {
return fputil::cast<float16>(is_neg ? -r : r);
};

if (LIBC_UNLIKELY(xbits.is_inf_or_nan())) {
if (xbits.is_nan()) {
if (xbits.is_signaling_nan()) {
fputil::raise_except_if_required(FE_INVALID);
return FPBits::quiet_nan().get_val();
}
return x;
}
// atanpi(±∞) = ±0.5
return signed_result(0.5);
}

if (LIBC_UNLIKELY(xbits.is_zero()))
return x;

double x_abs = fputil::cast<double>(xbits.abs().get_val());

if (LIBC_UNLIKELY(x_abs == 1.0))
return signed_result(0.25);

// polynomial coefficients for atan(x)/pi taylor series
// generated using sympy: series(atan(x)/pi, x, 0, 17)
constexpr double POLY_COEFFS[] = {
0x1.45f306dc9c889p-2, // x^1: 1/pi
-0x1.b2995e7b7b60bp-4, // x^3: -1/(3*pi)
0x1.04c26be3b06ccp-4, // x^5: 1/(5*pi)
-0x1.7483758e69c08p-5, // x^7: -1/(7*pi)
0x1.21bb945252403p-5, // x^9: 1/(9*pi)
-0x1.da1bace3cc68ep-6, // x^11: -1/(11*pi)
0x1.912b1c2336cf2p-6, // x^13: 1/(13*pi)
-0x1.5bade52f95e7p-6, // x^15: -1/(15*pi)
};

// evaluate atan(x)/pi using polynomial approximation, valid for |x| <= 0.5
constexpr auto atanpi_eval = [](double x) -> double {
double x_sq = x * x;
return x * fputil::polyeval(x_sq, POLY_COEFFS[0], POLY_COEFFS[1],
POLY_COEFFS[2], POLY_COEFFS[3], POLY_COEFFS[4],
POLY_COEFFS[5], POLY_COEFFS[6], POLY_COEFFS[7]);
};

// Case 1: |x| <= 0.5 - Direct polynomial evaluation
if (LIBC_LIKELY(x_abs <= 0.5)) {
double result = atanpi_eval(x_abs);
return signed_result(result);
}

// case 2: 0.5 < |x| <= 1 - use double-angle reduction
// atan(x) = 2 * atan(x / (1 + sqrt(1 + x^2)))
// so atanpi(x) = 2 * atanpi(x') where x' = x / (1 + sqrt(1 + x^2))
if (x_abs <= 1.0) {
double x2 = x_abs * x_abs;
double sqrt_term = fputil::sqrt<double>(1.0 + x2);
double x_prime = x_abs / (1.0 + sqrt_term);
double result = 2.0 * atanpi_eval(x_prime);
return signed_result(result);
}

// case 3: |x| > 1 - use reciprocal transformation
// atan(x) = pi/2 - atan(1/x) for x > 0
// so atanpi(x) = 1/2 - atanpi(1/x)
double x_recip = 1.0 / x_abs;
double result;

// if 1/|x| > 0.5, we need to apply Case 2 transformation to 1/|x|
if (x_recip > 0.5) {
double x_sq_recip = x_recip * x_recip;
double sqrt_term = fputil::sqrt<double>(1.0 + x_sq_recip);
double x_prime = x_recip / (1.0 + sqrt_term);
result = fputil::multiply_add(-2.0, atanpi_eval(x_prime), 0.5);
} else {
// direct evaluation since 1/|x| <= 0.5
result = 0.5 - atanpi_eval(x_recip);
}

return signed_result(result);
}

} // namespace LIBC_NAMESPACE_DECL
11 changes: 11 additions & 0 deletions libc/test/src/math/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2169,6 +2169,17 @@ add_fp_unittest(
libc.src.math.atanhf16
)

add_fp_unittest(
atanpif16_test
NEED_MPFR
SUITE
libc-math-unittests
SRCS
atanpif16_test.cpp
DEPENDS
libc.src.math.atanpif16
)

add_fp_unittest(
fmul_test
NEED_MPFR
Expand Down
40 changes: 40 additions & 0 deletions libc/test/src/math/atanpif16_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//===-- Exhaustive test for atanpif16 -------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "src/math/atanpif16.h"
#include "test/UnitTest/FPMatcher.h"
#include "test/UnitTest/Test.h"
#include "utils/MPFRWrapper/MPFRUtils.h"

using LlvmLibcAtanpif16Test = LIBC_NAMESPACE::testing::FPTest<float16>;

namespace mpfr = LIBC_NAMESPACE::testing::mpfr;

// Range: [0, Inf]
static constexpr uint16_t POS_START = 0x0000U;
static constexpr uint16_t POS_STOP = 0x7c00U;

// Range: [-Inf, 0]
static constexpr uint16_t NEG_START = 0x8000U;
static constexpr uint16_t NEG_STOP = 0xfc00U;

TEST_F(LlvmLibcAtanpif16Test, PositiveRange) {
for (uint16_t v = POS_START; v <= POS_STOP; ++v) {
float16 x = FPBits(v).get_val();
EXPECT_MPFR_MATCH_ALL_ROUNDING(mpfr::Operation::Atanpi, x,
LIBC_NAMESPACE::atanpif16(x), 0.5);
}
}

TEST_F(LlvmLibcAtanpif16Test, NegativeRange) {
for (uint16_t v = NEG_START; v <= NEG_STOP; ++v) {
float16 x = FPBits(v).get_val();
EXPECT_MPFR_MATCH_ALL_ROUNDING(mpfr::Operation::Atanpi, x,
LIBC_NAMESPACE::atanpif16(x), 0.5);
}
}
12 changes: 12 additions & 0 deletions libc/test/src/math/smoke/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3965,6 +3965,18 @@ add_fp_unittest(
libc.src.__support.FPUtil.cast
)

add_fp_unittest(
atanpif16_test
NEED_MPFR
SUITE
libc-math-unittests
SRCS
atanpif16_test.cpp
DEPENDS
libc.src.math.atanpif16
libc.src.errno.errno
)

add_fp_unittest(
asinhf_test
SUITE
Expand Down
64 changes: 64 additions & 0 deletions libc/test/src/math/smoke/atanpif16_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//===-- Unittests for atanpif16 -------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "src/__support/libc_errno.h"
#include "src/math/atanpif16.h"
#include "test/UnitTest/FPMatcher.h"

using LIBC_NAMESPACE::cpp::array;
using LlvmLibcAtanpif16Test = LIBC_NAMESPACE::testing::FPTest<float16>;

TEST_F(LlvmLibcAtanpif16Test, SpecialNumbers) {
// zero
EXPECT_FP_EQ(zero, LIBC_NAMESPACE::atanpif16(zero));
EXPECT_FP_EQ(neg_zero, LIBC_NAMESPACE::atanpif16(neg_zero));

// NaN inputs
EXPECT_FP_EQ(FPBits::quiet_nan().get_val(),
LIBC_NAMESPACE::atanpif16(FPBits::quiet_nan().get_val()));

EXPECT_FP_EQ(aNaN, LIBC_NAMESPACE::atanpif16(aNaN));

// infinity inputs -> should return +/-0.5
EXPECT_FP_EQ(0.5f16, LIBC_NAMESPACE::atanpif16(inf));
EXPECT_FP_EQ(-0.5f16, LIBC_NAMESPACE::atanpif16(neg_inf));
}

TEST_F(LlvmLibcAtanpif16Test, SymmetryProperty) {
// Test that atanpi(-x) = -atanpi(x)
constexpr float16 TEST_VALS[] = {0.1f16, 0.25f16, 0.5f16, 0.75f16,
1.0f16, 1.5f16, 2.0f16, 5.0f16,
10.0f16, 50.0f16, 100.0f16, 1000.0f16};

for (float16 x : TEST_VALS) {
FPBits neg_x_bits(x);
neg_x_bits.set_sign(Sign::NEG);
float16 neg_x = neg_x_bits.get_val();

float16 pos_result = LIBC_NAMESPACE::atanpif16(x);
float16 neg_result = LIBC_NAMESPACE::atanpif16(neg_x);

EXPECT_FP_EQ(pos_result, FPBits(neg_result).abs().get_val());
}
}

TEST_F(LlvmLibcAtanpif16Test, MonotonicityProperty) {
// Test that atanpi is monotonically increasing
constexpr array<float16, 15> TEST_VALS = {
-1000.0f16, -100.0f16, -10.0f16, -2.0f16, -1.0f16,
-0.5f16, -0.1f16, 0.0f16, 0.1f16, 0.5f16,
1.0f16, 2.0f16, 10.0f16, 100.0f16, 1000.0f16};
for (size_t i = 0; i < TEST_VALS.size() - 1; ++i) {
float16 x1 = TEST_VALS[i];
float16 x2 = TEST_VALS[i + 1];
float16 result1 = LIBC_NAMESPACE::atanpif16(x1);
float16 result2 = LIBC_NAMESPACE::atanpif16(x2);

EXPECT_TRUE(result1 < result2);
}
}
Loading
Loading