diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt index ac281e8d39066..948a0b648e20a 100644 --- a/libc/config/linux/x86_64/entrypoints.txt +++ b/libc/config/linux/x86_64/entrypoints.txt @@ -652,9 +652,10 @@ endif() if(LIBC_TYPES_HAS_FLOAT16) list(APPEND TARGET_LIBM_ENTRYPOINTS # math.h C23 _Float16 entrypoints - libc.src.math.asinf16 libc.src.math.acosf16 libc.src.math.acoshf16 + libc.src.math.asinf16 + libc.src.math.asinhf16 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 16903a40c03fa..23d010e2ab5d7 100644 --- a/libc/docs/headers/math/index.rst +++ b/libc/docs/headers/math/index.rst @@ -257,7 +257,7 @@ Higher Math Functions +-----------+------------------+-----------------+------------------------+----------------------+------------------------+------------------------+----------------------------+ | asin | |check| | | | |check| | | 7.12.4.2 | F.10.1.2 | +-----------+------------------+-----------------+------------------------+----------------------+------------------------+------------------------+----------------------------+ -| asinh | |check| | | | | | 7.12.5.2 | F.10.2.2 | +| asinh | |check| | | | |check| | | 7.12.5.2 | F.10.2.2 | +-----------+------------------+-----------------+------------------------+----------------------+------------------------+------------------------+----------------------------+ | asinpi | | | | | | 7.12.4.9 | F.10.1.9 | +-----------+------------------+-----------------+------------------------+----------------------+------------------------+------------------------+----------------------------+ diff --git a/libc/include/math.yaml b/libc/include/math.yaml index bd29eb0213c4f..3a06bcfc4f43e 100644 --- a/libc/include/math.yaml +++ b/libc/include/math.yaml @@ -59,6 +59,13 @@ functions: return_type: float arguments: - type: float + - name: asinhf16 + standards: + - stdc + return_type: _Float16 + arguments: + - type: _Float16 + guard: LIBC_TYPES_HAS_FLOAT16 - name: atan standards: - stdc diff --git a/libc/src/math/CMakeLists.txt b/libc/src/math/CMakeLists.txt index 92c80a1053c9e..9fec978ece2bd 100644 --- a/libc/src/math/CMakeLists.txt +++ b/libc/src/math/CMakeLists.txt @@ -54,6 +54,7 @@ add_math_entrypoint_object(asinf16) add_math_entrypoint_object(asinh) add_math_entrypoint_object(asinhf) +add_math_entrypoint_object(asinhf16) add_math_entrypoint_object(atan) add_math_entrypoint_object(atanf) diff --git a/libc/src/math/asinhf16.h b/libc/src/math/asinhf16.h new file mode 100644 index 0000000000000..bb40e208d7ee3 --- /dev/null +++ b/libc/src/math/asinhf16.h @@ -0,0 +1,21 @@ +//===-- Implementation header for asinhf16 ----------------------*- 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_ASINHF16_H +#define LLVM_LIBC_SRC_MATH_ASINHF16_H + +#include "src/__support/macros/config.h" +#include "src/__support/macros/properties/types.h" + +namespace LIBC_NAMESPACE_DECL { + +float16 asinhf16(float16 x); + +} // namespace LIBC_NAMESPACE_DECL + +#endif // LLVM_LIBC_SRC_MATH_ASINHF16_H diff --git a/libc/src/math/generic/CMakeLists.txt b/libc/src/math/generic/CMakeLists.txt index 77c6244c5e5da..d3454803df377 100644 --- a/libc/src/math/generic/CMakeLists.txt +++ b/libc/src/math/generic/CMakeLists.txt @@ -3980,6 +3980,27 @@ add_entrypoint_object( libc.src.__support.macros.optimization ) +add_entrypoint_object( + asinhf16 + SRCS + asinhf16.cpp + HDRS + ../asinhf16.h + DEPENDS + .explogxf + libc.hdr.fenv_macros + libc.src.__support.FPUtil.cast + libc.src.__support.FPUtil.except_value_utils + 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.rounding_mode + libc.src.__support.FPUtil.sqrt + libc.src.__support.macros.optimization + libc.src.__support.macros.properties.types +) + add_entrypoint_object( atanhf SRCS diff --git a/libc/src/math/generic/asinhf16.cpp b/libc/src/math/generic/asinhf16.cpp new file mode 100644 index 0000000000000..78786320b5f71 --- /dev/null +++ b/libc/src/math/generic/asinhf16.cpp @@ -0,0 +1,107 @@ +//===-- Half-precision asinh(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/asinhf16.h" +#include "explogxf.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/rounding_mode.h" +#include "src/__support/FPUtil/sqrt.h" +#include "src/__support/common.h" +#include "src/__support/macros/config.h" +#include "src/__support/macros/optimization.h" + +namespace LIBC_NAMESPACE_DECL { + +#ifndef LIBC_MATH_HAS_SKIP_ACCURATE_PASS +static constexpr size_t N_EXCEPTS = 8; + +static constexpr fputil::ExceptValues ASINHF16_EXCEPTS{{ + // (input, RZ output, RU offset, RD offset, RN offset) + + // x = 0x1.da4p-2, asinhf16(x) = 0x1.ca8p-2 (RZ) + {0x3769, 0x372a, 1, 0, 1}, + // x = 0x1.d6cp-1, asinhf16(x) = 0x1.a58p-1 (RZ) + {0x3b5b, 0x3a96, 1, 0, 0}, + // x = 0x1.c7cp+3, asinhf16(x) = 0x1.accp+1 (RZ) + {0x4b1f, 0x42b3, 1, 0, 0}, + // x = 0x1.26cp+4, asinhf16(x) = 0x1.cd8p+1 (RZ) + {0x4c9b, 0x4336, 1, 0, 1}, + // x = -0x1.da4p-2, asinhf16(x) = -0x1.ca8p-2 (RZ) + {0xb769, 0xb72a, 0, 1, 1}, + // x = -0x1.d6cp-1, asinhf16(x) = -0x1.a58p-1 (RZ) + {0xbb5b, 0xba96, 0, 1, 0}, + // x = -0x1.c7cp+3, asinhf16(x) = -0x1.accp+1 (RZ) + {0xcb1f, 0xc2b3, 0, 1, 0}, + // x = -0x1.26cp+4, asinhf16(x) = -0x1.cd8p+1 (RZ) + {0xcc9b, 0xc336, 0, 1, 1}, +}}; +#endif // !LIBC_MATH_HAS_SKIP_ACCURATE_PASS + +LLVM_LIBC_FUNCTION(float16, asinhf16, (float16 x)) { + using FPBits = fputil::FPBits; + FPBits xbits(x); + + uint16_t x_u = xbits.uintval(); + uint16_t x_abs = x_u & 0x7fff; + + if (LIBC_UNLIKELY(xbits.is_inf_or_nan())) { + if (xbits.is_signaling_nan()) { + fputil::raise_except_if_required(FE_INVALID); + return FPBits::quiet_nan().get_val(); + } + + return x; + } + +#ifndef LIBC_MATH_HAS_SKIP_ACCURATE_PASS + // Handle exceptional values + if (auto r = ASINHF16_EXCEPTS.lookup(x_u); LIBC_UNLIKELY(r.has_value())) + return r.value(); +#endif // !LIBC_MATH_HAS_SKIP_ACCURATE_PASS + + float xf = x; + const float SIGN[2] = {1.0f, -1.0f}; + float x_sign = SIGN[x_u >> 15]; + + // |x| <= 0.25 + if (LIBC_UNLIKELY(x_abs <= 0x3400)) { + // when |x| < 0x1.718p-5, asinhf16(x) = x. Adjust by 1 ULP for certain + // rounding types. + if (LIBC_UNLIKELY(x_abs < 0x29c6)) { + int rounding = fputil::quick_get_round(); + if ((rounding == FE_UPWARD || rounding == FE_TOWARDZERO) && xf < 0) + return fputil::cast(xf + 0x1p-24f); + if ((rounding == FE_DOWNWARD || rounding == FE_TOWARDZERO) && xf > 0) + return fputil::cast(xf - 0x1p-24f); + return fputil::cast(xf); + } + + float x_sq = xf * xf; + // Generated by Sollya with: + // > P = fpminimax(asinh(x)/x, [|0, 2, 4, 6, 8|], [|SG...|], [0, 2^-2]); + // The last coefficient 0x1.bd114ep-6f has been changed to 0x1.bd114ep-5f + // for better accuracy. + float p = fputil::polyeval(x_sq, 1.0f, -0x1.555552p-3f, 0x1.332f6ap-4f, + -0x1.6c53dep-5f, 0x1.bd114ep-5f); + + return fputil::cast(xf * p); + } + + // General case: asinh(x) = ln(x + sqrt(x^2 + 1)) + float sqrt_term = fputil::sqrt(fputil::multiply_add(xf, xf, 1.0f)); + return fputil::cast( + x_sign * log_eval(fputil::multiply_add(xf, x_sign, sqrt_term))); +} + +} // namespace LIBC_NAMESPACE_DECL diff --git a/libc/test/src/math/CMakeLists.txt b/libc/test/src/math/CMakeLists.txt index 6ca4163f07949..9a73f9fe07597 100644 --- a/libc/test/src/math/CMakeLists.txt +++ b/libc/test/src/math/CMakeLists.txt @@ -2175,6 +2175,17 @@ add_fp_unittest( libc.src.__support.FPUtil.fp_bits ) +add_fp_unittest( + asinhf16_test + NEED_MPFR + SUITE + libc-math-unittests + SRCS + asinhf16_test.cpp + DEPENDS + libc.src.math.asinhf16 +) + add_fp_unittest( acoshf_test NEED_MPFR diff --git a/libc/test/src/math/asinhf16_test.cpp b/libc/test/src/math/asinhf16_test.cpp new file mode 100644 index 0000000000000..929d13713d197 --- /dev/null +++ b/libc/test/src/math/asinhf16_test.cpp @@ -0,0 +1,42 @@ +//===-- Exhaustive test for asinhf16 --------------------------------------===// +// +// 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/asinhf16.h" +#include "test/UnitTest/FPMatcher.h" +#include "test/UnitTest/Test.h" +#include "utils/MPFRWrapper/MPFRUtils.h" + +using LlvmLibcAsinhf16Test = LIBC_NAMESPACE::testing::FPTest; + +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(LlvmLibcAsinhf16Test, PositiveRange) { + for (uint16_t v = POS_START; v <= POS_STOP; ++v) { + float16 x = FPBits(v).get_val(); + + EXPECT_MPFR_MATCH_ALL_ROUNDING(mpfr::Operation::Asinh, x, + LIBC_NAMESPACE::asinhf16(x), 0.5); + } +} + +TEST_F(LlvmLibcAsinhf16Test, NegativeRange) { + for (uint16_t v = NEG_START; v <= NEG_STOP; ++v) { + float16 x = FPBits(v).get_val(); + + EXPECT_MPFR_MATCH_ALL_ROUNDING(mpfr::Operation::Asinh, x, + LIBC_NAMESPACE::asinhf16(x), 0.5); + } +} diff --git a/libc/test/src/math/smoke/CMakeLists.txt b/libc/test/src/math/smoke/CMakeLists.txt index 660b68687d63c..b7601735c1109 100644 --- a/libc/test/src/math/smoke/CMakeLists.txt +++ b/libc/test/src/math/smoke/CMakeLists.txt @@ -3946,6 +3946,17 @@ add_fp_unittest( libc.src.__support.FPUtil.fp_bits ) +add_fp_unittest( + asinhf16_test + SUITE + libc-math-smoke-tests + SRCS + asinhf16_test.cpp + DEPENDS + libc.src.errno.errno + libc.src.math.asinhf16 +) + add_fp_unittest( acoshf_test SUITE diff --git a/libc/test/src/math/smoke/asinhf16_test.cpp b/libc/test/src/math/smoke/asinhf16_test.cpp new file mode 100644 index 0000000000000..dcaab217331c7 --- /dev/null +++ b/libc/test/src/math/smoke/asinhf16_test.cpp @@ -0,0 +1,35 @@ +//===-- Unittests for asinhf16 --------------------------------------------===// +// +// 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/asinhf16.h" +#include "test/UnitTest/FPMatcher.h" +#include "test/UnitTest/Test.h" + +using LlvmLibcAsinhf16Test = LIBC_NAMESPACE::testing::FPTest; + +TEST_F(LlvmLibcAsinhf16Test, SpecialNumbers) { + LIBC_NAMESPACE::libc_errno = 0; + EXPECT_FP_EQ(aNaN, LIBC_NAMESPACE::asinhf16(aNaN)); + EXPECT_MATH_ERRNO(0); + + EXPECT_FP_EQ_WITH_EXCEPTION(aNaN, LIBC_NAMESPACE::asinhf16(sNaN), FE_INVALID); + EXPECT_MATH_ERRNO(0); + + EXPECT_FP_EQ(zero, LIBC_NAMESPACE::asinhf16(zero)); + EXPECT_MATH_ERRNO(0); + + EXPECT_FP_EQ(neg_zero, LIBC_NAMESPACE::asinhf16(neg_zero)); + EXPECT_MATH_ERRNO(0); + + EXPECT_FP_EQ(inf, LIBC_NAMESPACE::asinhf16(inf)); + EXPECT_MATH_ERRNO(0); + + EXPECT_FP_EQ(neg_inf, LIBC_NAMESPACE::asinhf16(neg_inf)); + EXPECT_MATH_ERRNO(0); +}