Skip to content

[libc][math][c23] implement C23 math function asinpif16 #146226

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 18 commits into
base: main
Choose a base branch
from

Conversation

hulxv
Copy link
Member

@hulxv hulxv commented Jun 28, 2025

The function is implemented using the following Taylor series that's generated using python-sympy, and it is very accurate for |x| $$\in [0, 0.5]$$ and has been verified using Geogebra. The range reduction is used for the rest range (0.5, 1].

$$ \frac{\arcsin(x)}{\pi} \approx \begin{aligned}[t] & 0.318309886183791x \\ & + 0.0530516476972984x^3 \\ & + 0.0238732414637843x^5 \\ & + 0.0142102627760621x^7 \\ & + 0.00967087327815336x^9 \\ & + 0.00712127941391293x^{11} \\ & + 0.00552355646848375x^{13} \\ & + 0.00444514782463692x^{15} \\ & + 0.00367705242846804x^{17} \\ & + 0.00310721681820837x^{19} + O(x^{21}) \end{aligned} $$

Geogebra graph

28-06-2025-1913-eDP-1

Closes #132210

@llvmbot
Copy link
Member

llvmbot commented Jun 28, 2025

@llvm/pr-subscribers-libc

Author: Mohamed Emad (hulxv)

Changes

The function is implemented using the following Taylor series that's generated using python-sympy, and it is very accurate for x $$\in [0, 0.5]$$ and has been verified using Geogebra. The range reduction is used for the rest range (0.5, 1].

$$
\frac{\arcsin(x)}{\pi} \approx
\begin{aligned}[t]
& 0.318309886183791x \
& + 0.0530516476972984x^3 \
& + 0.0238732414637843x^5 \
& + 0.0142102627760621x^7 \
& + 0.00967087327815336x^9 \
& + 0.00712127941391293x^{11} \
& + 0.00552355646848375x^{13} \
& + 0.00444514782463692x^{15} \
& + 0.00367705242846804x^{17} \
& + 0.00310721681820837x^{19} + O(x^{21})
\end{aligned}
$$

Geogebra graph

28-06-2025-1913-eDP-1

Closes #132210


Full diff: https://github.com/llvm/llvm-project/pull/146226.diff

10 Files Affected:

  • (modified) libc/config/linux/aarch64/entrypoints.txt (+1)
  • (modified) libc/config/linux/x86_64/entrypoints.txt (+1)
  • (modified) libc/docs/headers/math/index.rst (+1-1)
  • (modified) libc/include/math.yaml (+8-1)
  • (modified) libc/src/math/CMakeLists.txt (+2)
  • (added) libc/src/math/asinpif16.h (+21)
  • (modified) libc/src/math/generic/CMakeLists.txt (+16)
  • (added) libc/src/math/generic/asinpif16.cpp (+177)
  • (modified) libc/test/src/math/CMakeLists.txt (+13)
  • (added) libc/test/src/math/asinpif16_test.cpp (+110)
diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt
index be5f5a66016b5..240d48f5bcdb8 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -651,6 +651,7 @@ if(LIBC_TYPES_HAS_FLOAT16)
   list(APPEND TARGET_LIBM_ENTRYPOINTS
     # math.h C23 _Float16 entrypoints
     # libc.src.math.acoshf16
+    libc.src.math.asinpif16
     libc.src.math.canonicalizef16
     libc.src.math.ceilf16
     libc.src.math.copysignf16
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index 73dfeae1a2c94..c9e5b0096099f 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -664,6 +664,7 @@ if(LIBC_TYPES_HAS_FLOAT16)
     libc.src.math.acoshf16
     libc.src.math.asinf16
     libc.src.math.asinhf16
+    libc.src.math.asinpif16
     libc.src.math.canonicalizef16
     libc.src.math.ceilf16
     libc.src.math.copysignf16
diff --git a/libc/docs/headers/math/index.rst b/libc/docs/headers/math/index.rst
index 947bd4b60b391..aaacc3ca92832 100644
--- a/libc/docs/headers/math/index.rst
+++ b/libc/docs/headers/math/index.rst
@@ -259,7 +259,7 @@ Higher Math Functions
 +-----------+------------------+-----------------+------------------------+----------------------+------------------------+------------------------+----------------------------+
 | asinh     | |check|          |                 |                        | |check|              |                        | 7.12.5.2               | F.10.2.2                   |
 +-----------+------------------+-----------------+------------------------+----------------------+------------------------+------------------------+----------------------------+
-| asinpi    |                  |                 |                        |                      |                        | 7.12.4.9               | F.10.1.9                   |
+| asinpi    |                  |                 |                        | |check|              |                        | 7.12.4.9               | F.10.1.9                   |
 +-----------+------------------+-----------------+------------------------+----------------------+------------------------+------------------------+----------------------------+
 | atan      | |check|          | 1 ULP           |                        |                      |                        | 7.12.4.3               | F.10.1.3                   |
 +-----------+------------------+-----------------+------------------------+----------------------+------------------------+------------------------+----------------------------+
diff --git a/libc/include/math.yaml b/libc/include/math.yaml
index fef829422244d..b49c17b871a93 100644
--- a/libc/include/math.yaml
+++ b/libc/include/math.yaml
@@ -71,7 +71,14 @@ functions:
       - stdc
     return_type: double
     arguments:
-      - type: double
+      - type: double 
+  - name: asinpif16
+    standards:
+      - stdc
+    return_type: _Float16
+    arguments:
+      - type: _Float16
+    guard: LIBC_TYPES_HAS_FLOAT16
   - name: atan2
     standards:
       - stdc
diff --git a/libc/src/math/CMakeLists.txt b/libc/src/math/CMakeLists.txt
index d177ff79141c0..e7ccb3726c54c 100644
--- a/libc/src/math/CMakeLists.txt
+++ b/libc/src/math/CMakeLists.txt
@@ -56,6 +56,8 @@ add_math_entrypoint_object(asinh)
 add_math_entrypoint_object(asinhf)
 add_math_entrypoint_object(asinhf16)
 
+add_math_entrypoint_object(asinpif16)
+
 add_math_entrypoint_object(atan)
 add_math_entrypoint_object(atanf)
 
diff --git a/libc/src/math/asinpif16.h b/libc/src/math/asinpif16.h
new file mode 100644
index 0000000000000..67ccb4ff4ac3d
--- /dev/null
+++ b/libc/src/math/asinpif16.h
@@ -0,0 +1,21 @@
+//===-- Implementation header for asinpif16 ---------------------*- 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_ASINFPI16_H
+#define LLVM_LIBC_SRC_MATH_ASINFPI16_H
+
+#include "src/__support/macros/config.h"
+#include "src/__support/macros/properties/types.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+float16 asinpif16(float16 x);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_MATH_ASINPIF16_H
diff --git a/libc/src/math/generic/CMakeLists.txt b/libc/src/math/generic/CMakeLists.txt
index adbed5b2de48c..76accf72058d3 100644
--- a/libc/src/math/generic/CMakeLists.txt
+++ b/libc/src/math/generic/CMakeLists.txt
@@ -4017,6 +4017,22 @@ add_entrypoint_object(
     libc.src.__support.macros.properties.types
 )
 
+add_entrypoint_object(
+  asinpif16
+  SRCS
+    asinpif16.cpp
+  HDRS
+    ../asinpif16.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.macros.optimization
+)
+
 add_entrypoint_object(
   atanhf
   SRCS
diff --git a/libc/src/math/generic/asinpif16.cpp b/libc/src/math/generic/asinpif16.cpp
new file mode 100644
index 0000000000000..34b0b7e7779fc
--- /dev/null
+++ b/libc/src/math/generic/asinpif16.cpp
@@ -0,0 +1,177 @@
+//===-- Half-precision asinf16(x) 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/asinpif16.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/except_value_utils.h"
+#include "src/__support/FPUtil/multiply_add.h"
+#include "src/__support/FPUtil/sqrt.h"
+#include "src/__support/macros/optimization.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+static constexpr float16 ONE_OVER_TWO = 0.5f16;
+
+#ifndef LIBC_MATH_HAS_SKIP_ACCURATE_PASS
+static constexpr size_t N_ASINFPI_EXCEPTS = 3;
+static constexpr float16 ONE_OVER_SIX = 0.166748046875f16;
+
+static constexpr fputil::ExceptValues<float16, N_ASINFPI_EXCEPTS>
+    ASINFPI_EXCEPTS{{
+        // (input_hex, RZ_output_hex, RU_offset, RD_offset, RN_offset)
+        // x = 0.0, asinfpi(0.0) = 0.0
+        {0x0000, 0x0000, 0, 0, 0},
+
+        // x = 1.0, asinfpi(1.0) = 0.5
+        {(fputil::FPBits<float16>(1.0f16)).uintval(),
+         (fputil::FPBits<float16>(ONE_OVER_TWO)).uintval(), 0, 0, 0},
+
+        // x = 0.5, asinfpi(0.5) = 1/6
+        {(fputil::FPBits<float16>(0.5f16)).uintval(),
+         (fputil::FPBits<float16>(ONE_OVER_SIX)).uintval(), 0, 0, 0},
+
+    }};
+#endif // !LIBC_MATH_HAS_SKIP_ACCURATE_PASS
+
+LLVM_LIBC_FUNCTION(float16, asinpif16, (float16 x)) {
+  using FPBits = fputil::FPBits<float16>;
+
+  FPBits xbits(x);
+  uint16_t x_uint = xbits.uintval();
+  bool is_neg = static_cast<bool>(x_uint >> 15);
+  float16 x_abs = is_neg ? -x : x;
+
+  auto __signed_result = [is_neg](float16 r) -> float16 {
+    return is_neg ? -r : r;
+  };
+
+  if (LIBC_UNLIKELY(x_abs > 1.0f16)) {
+    // aspinf16(NaN) = 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;
+    }
+
+    // 1 < |x| <= +/-inf
+    fputil::raise_except_if_required(FE_INVALID);
+    fputil::set_errno_if_required(EDOM);
+
+    return FPBits::quiet_nan().get_val();
+  }
+
+#ifndef LIBC_MATH_HAS_SKIP_ACCURATE_PASS
+  // exceptional values
+  if (auto r = ASINFPI_EXCEPTS.lookup(x_uint); LIBC_UNLIKELY(r.has_value())) {
+    return (r.value());
+  }
+#endif // !LIBC_MATH_HAS_SKIP_ACCURATE_PASS
+
+  // zero
+  if (LIBC_UNLIKELY(x_abs == 0.0f16)) {
+    return 0.0f16;
+  }
+
+  // +/-1.0
+  // if x is +/-1.0, return +/-0.5
+  if (LIBC_UNLIKELY(x_abs == 1.0f16)) {
+    return fputil::cast<float16>(__signed_result(ONE_OVER_TWO));
+  }
+
+  // the coefficients for the polynomial approximation of asin(x)/pi in the
+  // range [0, 0.5] extracted using python-sympy
+  //
+  // Python code to generate the coefficients:
+  //  > from sympy import *
+  //  > import math
+  //  > x = symbols('x')
+  //  > print(series(asin(x)/math.pi, x, 0, 21))
+  //
+  // OUTPUT:
+  //
+  // 0.318309886183791*x + 0.0530516476972984*x**3 + 0.0238732414637843*x**5 +
+  // 0.0142102627760621*x**7 + 0.00967087327815336*x**9 +
+  // 0.00712127941391293*x**11 + 0.00552355646848375*x**13 +
+  // 0.00444514782463692*x**15 + 0.00367705242846804*x**17 +
+  // 0.00310721681820837*x**19 + O(x**21)
+  //
+  // it's very accurate in the range [0, 0.5] and has a maximum error of
+  // 0.0000000000000001 in the range [0, 0.5].
+  static constexpr float16 POLY_COEFFS[10] = {
+      0.318309886183791f16,   // x^1
+      0.0530516476972984f16,  // x^3
+      0.0238732414637843f16,  // x^5
+      0.0142102627760621f16,  // x^7
+      0.00967087327815336f16, // x^9
+      0.00712127941391293f16, // x^11
+      0.00552355646848375f16, // x^13
+      0.00444514782463692f16, // x^15
+      0.00367705242846804f16, // x^17
+      0.00310721681820837f16  // x^19
+  };
+
+  // polynomial evaluation using horner's method
+  // work only for |x| in [0, 0.5]
+  auto __asinpi_polyeval = [](float16 xsq) -> float16 {
+    return fputil::polyeval(xsq, POLY_COEFFS[0], POLY_COEFFS[1], POLY_COEFFS[2],
+                            POLY_COEFFS[3], POLY_COEFFS[4], POLY_COEFFS[5],
+                            POLY_COEFFS[6], POLY_COEFFS[7], POLY_COEFFS[8],
+                            POLY_COEFFS[9]);
+  };
+
+  // if |x| <= 0.5:
+  if (LIBC_UNLIKELY(x_abs <= ONE_OVER_TWO)) {
+    // Use polynomial approximation of asin(x)/pi in the range [0, 0.5]
+    float16 xsq = x * x;
+    float16 result = x * __asinpi_polyeval(xsq);
+    return fputil::cast<float16>(__signed_result(result));
+  }
+
+  // If |x| > 0.5, we need to use the range reduction method:
+  //    y = asin(x) => x = sin(y)
+  //      because: sin(a) = cos(pi/2 - a)
+  //      therefore:
+  //    x = cos(pi/2 - y)
+  //      let z = pi/2 - y,
+  //    x = cos(z)
+  //      becuase: cos(2a) = 1 - 2 * sin^2(a), z = 2a, a = z/2
+  //      therefore:
+  //    cos(z) = 1 - 2 * sin^2(z/2)
+  //    sin(z/2) = sqrt((1 - cos(z))/2)
+  //    sin(z/2) = sqrt((1 - x)/2)
+  //      let u = (1 - x)/2
+  //      then:
+  //    sin(z/2) = sqrt(u)
+  //    z/2 = asin(sqrt(u))
+  //    z = 2 * asin(sqrt(u))
+  //    pi/2 - y = 2 * asin(sqrt(u))
+  //    y = pi/2 - 2 * asin(sqrt(u))
+  //    y/pi = 1/2 - 2 * asin(sqrt(u))/pi
+  //
+  // Finally, we can write:
+  //   asinpi(x) = 1/2 - 2 * asinpi(sqrt(u))
+  //     where u = (1 - x) /2
+  //             = 0.5 - 0.5 * x
+  //             = multiply_add(-0.5, x, 0.5)
+
+  float16 u = fputil::multiply_add(-ONE_OVER_TWO, x_abs, ONE_OVER_TWO);
+  float16 u_sqrt = fputil::sqrt<float16>(u);
+  float16 asinpi_sqrt_u = u_sqrt * __asinpi_polyeval(u);
+  float16 result = fputil::multiply_add(-2.0f16, asinpi_sqrt_u, ONE_OVER_TWO);
+
+  return fputil::cast<float16>(__signed_result(result));
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/test/src/math/CMakeLists.txt b/libc/test/src/math/CMakeLists.txt
index 7ee8b86135557..bd62b76817ceb 100644
--- a/libc/test/src/math/CMakeLists.txt
+++ b/libc/test/src/math/CMakeLists.txt
@@ -2245,6 +2245,19 @@ add_fp_unittest(
     libc.src.math.asinf16  
 )
 
+add_fp_unittest(
+  asinpif16_test
+  NEED_MPFR
+  SUITE
+    libc-math-unittests
+  SRCS
+    asinpif16_test.cpp
+  DEPENDS
+  libc.src.math.fabs
+  libc.src.math.asinpif16  
+  libc.src.__support.FPUtil.fp_bits
+)
+
 add_fp_unittest(
   acosf_test
   NEED_MPFR
diff --git a/libc/test/src/math/asinpif16_test.cpp b/libc/test/src/math/asinpif16_test.cpp
new file mode 100644
index 0000000000000..6d8f76568902e
--- /dev/null
+++ b/libc/test/src/math/asinpif16_test.cpp
@@ -0,0 +1,110 @@
+//===-- Unittests for asinpif16 -------------------------------------------===//
+//
+// 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/errno/libc_errno.h"
+#include "src/math/asinpif16.h"
+#include "src/math/fabs.h"
+#include "test/UnitTest/FPMatcher.h"
+
+#include <errno.h>
+#include <stdint.h>
+
+using LlvmLibcAsinpif16Test = LIBC_NAMESPACE::testing::FPTest<float16>;
+
+TEST_F(LlvmLibcAsinpif16Test, SpecialNumbers) {
+  using FPBits = LIBC_NAMESPACE::fputil::FPBits<float16>;
+
+  // zero
+  EXPECT_FP_EQ(0.0f16, LIBC_NAMESPACE::asinpif16(0.0f16));
+
+  // +/-1
+  EXPECT_FP_EQ(float16(0.5), LIBC_NAMESPACE::asinpif16(float16(1.0)));
+  EXPECT_FP_EQ(float16(-0.5), LIBC_NAMESPACE::asinpif16(float16(-1.0)));
+
+  // NaN inputs
+  EXPECT_FP_EQ(FPBits::quiet_nan().get_val(),
+               LIBC_NAMESPACE::asinpif16(FPBits::quiet_nan().get_val()));
+
+  EXPECT_FP_EQ(FPBits::quiet_nan().get_val(),
+               LIBC_NAMESPACE::asinpif16(FPBits::signaling_nan().get_val()));
+
+  // infinity inputs -> should return NaN
+  errno = 0;
+  EXPECT_FP_EQ(FPBits::quiet_nan().get_val(), LIBC_NAMESPACE::asinpif16(inf));
+  EXPECT_MATH_ERRNO(EDOM);
+
+  errno = 0;
+  EXPECT_FP_EQ(FPBits::quiet_nan().get_val(),
+               LIBC_NAMESPACE::asinpif16(neg_inf));
+  EXPECT_MATH_ERRNO(EDOM);
+}
+
+TEST_F(LlvmLibcAsinpif16Test, OutOfRange) {
+  using FPBits = LIBC_NAMESPACE::fputil::FPBits<float16>;
+
+  // Test values > 1
+  errno = 0;
+  EXPECT_FP_EQ(FPBits::quiet_nan().get_val(),
+               LIBC_NAMESPACE::asinpif16(float16(1.5)));
+  EXPECT_MATH_ERRNO(EDOM);
+
+  errno = 0;
+  EXPECT_FP_EQ(FPBits::quiet_nan().get_val(),
+               LIBC_NAMESPACE::asinpif16(float16(2.0)));
+  EXPECT_MATH_ERRNO(EDOM);
+
+  // Test values < -1
+  errno = 0;
+  EXPECT_FP_EQ(FPBits::quiet_nan().get_val(),
+               LIBC_NAMESPACE::asinpif16(float16(-1.5)));
+  EXPECT_MATH_ERRNO(EDOM);
+
+  errno = 0;
+  EXPECT_FP_EQ(FPBits::quiet_nan().get_val(),
+               LIBC_NAMESPACE::asinpif16(float16(-2.0)));
+  EXPECT_MATH_ERRNO(EDOM);
+
+  // Test maximum normal value (should be > 1 for float16)
+  errno = 0;
+  EXPECT_FP_EQ(FPBits::quiet_nan().get_val(),
+               LIBC_NAMESPACE::asinpif16(FPBits::max_normal().get_val()));
+  EXPECT_MATH_ERRNO(EDOM);
+}
+
+TEST_F(LlvmLibcAsinpif16Test, SymmetryProperty) {
+  // Test that asinpi(-x) = -asinpi(x)
+  constexpr float16 test_vals[] = {0.1f16, 0.25f16, 0.5f16, 0.75f16,
+                                   0.9f16, 0.99f16, 1.0f16};
+
+  for (float16 x : test_vals) {
+    if (x <= 1.0) {
+      float16 pos_result = LIBC_NAMESPACE::asinpif16(x);
+      float16 neg_result = LIBC_NAMESPACE::asinpif16(-x);
+
+      EXPECT_FP_EQ(pos_result, LIBC_NAMESPACE::fabs(neg_result));
+    }
+  }
+}
+
+TEST_F(LlvmLibcAsinpif16Test, RangeValidation) {
+  // Test that output is always in [-0.5, 0.5] for valid inputs
+  constexpr int num_tests = 1000;
+
+  for (int i = 0; i <= num_tests; ++i) {
+    float16 t = -1.0f16 + (2.0f16 * static_cast<float16>(i)) /
+                              static_cast<float16>(num_tests);
+
+    if (LIBC_NAMESPACE::fabs(t) <= 1.0) {
+      float16 result = LIBC_NAMESPACE::asinpif16(t);
+
+      // should be in [-0.5, 0.5]
+      EXPECT_TRUE(result >= -0.5f16);
+      EXPECT_TRUE(result <= 0.5f16);
+    }
+  }
+}

Copy link

github-actions bot commented Jun 28, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please split this test into a smoke test and an MPFR test that checks the entire input range for this function. See tests for existing float16 math functions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[libc][math][c23] Implement C23 math function asinpif16
3 participants