diff --git a/libc/config/darwin/arm/entrypoints.txt b/libc/config/darwin/arm/entrypoints.txt --- a/libc/config/darwin/arm/entrypoints.txt +++ b/libc/config/darwin/arm/entrypoints.txt @@ -105,6 +105,7 @@ libc.src.fenv.feupdateenv # math.h entrypoints + libc.src.math.atanhf libc.src.math.copysign libc.src.math.copysignf libc.src.math.copysignl diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt --- a/libc/config/linux/aarch64/entrypoints.txt +++ b/libc/config/linux/aarch64/entrypoints.txt @@ -149,6 +149,7 @@ libc.src.fenv.feupdateenv # math.h entrypoints + libc.src.math.atanhf libc.src.math.copysign libc.src.math.copysignf libc.src.math.copysignl diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt --- a/libc/config/linux/x86_64/entrypoints.txt +++ b/libc/config/linux/x86_64/entrypoints.txt @@ -149,6 +149,7 @@ libc.src.fenv.feupdateenv # math.h entrypoints + libc.src.math.atanhf libc.src.math.copysign libc.src.math.copysignf libc.src.math.copysignl diff --git a/libc/config/windows/entrypoints.txt b/libc/config/windows/entrypoints.txt --- a/libc/config/windows/entrypoints.txt +++ b/libc/config/windows/entrypoints.txt @@ -106,6 +106,7 @@ libc.src.fenv.feupdateenv # math.h entrypoints + libc.src.math.atanhf libc.src.math.copysign libc.src.math.copysignf libc.src.math.copysignl diff --git a/libc/spec/stdc.td b/libc/spec/stdc.td --- a/libc/spec/stdc.td +++ b/libc/spec/stdc.td @@ -479,6 +479,8 @@ FunctionSpec<"coshf", RetValSpec, [ArgSpec]>, FunctionSpec<"sinhf", RetValSpec, [ArgSpec]>, FunctionSpec<"tanhf", RetValSpec, [ArgSpec]>, + + FunctionSpec<"atanhf", RetValSpec, [ArgSpec]>, ] >; diff --git a/libc/src/math/CMakeLists.txt b/libc/src/math/CMakeLists.txt --- a/libc/src/math/CMakeLists.txt +++ b/libc/src/math/CMakeLists.txt @@ -66,6 +66,8 @@ -O3 ) +add_math_entrypoint_object(atanhf) + add_math_entrypoint_object(ceil) add_math_entrypoint_object(ceilf) add_math_entrypoint_object(ceill) diff --git a/libc/src/math/atanhf.h b/libc/src/math/atanhf.h new file mode 100644 --- /dev/null +++ b/libc/src/math/atanhf.h @@ -0,0 +1,18 @@ +//===-- Implementation header for atanhf ------------------------*- 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_ATANHF_H +#define LLVM_LIBC_SRC_MATH_ATANHF_H + +namespace __llvm_libc { + +float atanhf(float x); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_MATH_ATANHF_H diff --git a/libc/src/math/generic/CMakeLists.txt b/libc/src/math/generic/CMakeLists.txt --- a/libc/src/math/generic/CMakeLists.txt +++ b/libc/src/math/generic/CMakeLists.txt @@ -1225,3 +1225,20 @@ -O3 ) +add_entrypoint_object( + atanhf + SRCS + atanhf.cpp + HDRS + ../atanhf.h + DEPENDS + .supfuncf + libc.src.__support.FPUtil.fputil + libc.src.__support.FPUtil.multiply_add + libc.src.__support.FPUtil.nearest_integer + libc.src.__support.FPUtil.polyeval + libc.include.math + COMPILE_OPTIONS + -O3 +) + diff --git a/libc/src/math/generic/atanhf.cpp b/libc/src/math/generic/atanhf.cpp new file mode 100644 --- /dev/null +++ b/libc/src/math/generic/atanhf.cpp @@ -0,0 +1,57 @@ +//===-- Single-precision atanh 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/atanhf.h" +#include "src/__support/FPUtil/FPBits.h" +#include "src/math/generic/supfuncf.h" + +namespace __llvm_libc { + +LLVM_LIBC_FUNCTION(float, atanhf, (float x)) { + using FPBits = typename fputil::FPBits; + FPBits xbits(x); + bool sign = xbits.get_sign(); + uint32_t x_abs = xbits.uintval() & FPBits::FloatProp::EXP_MANT_MASK; + + // |x| >= 1.0 + if (unlikely(x_abs >= 0x3F80'0000U)) { + if (xbits.is_nan()) { + return x + 1.0f; + } + // |x| == 0 + if (x_abs == 0x3F80'0000U) { + fputil::set_except(FE_DIVBYZERO); + return with_errno(FPBits::inf(sign).get_val(), ERANGE); + } else { + fputil::set_except(FE_INVALID); + return with_errno( + FPBits::build_nan(1 << (fputil::MantissaWidth::VALUE - 1)), + EDOM); + } + } + + // |x| < ~0.10 + if (unlikely(x_abs <= 0x3dcc'0000U)) { + // |x| <= 2^-26 + if (unlikely(x_abs <= 0x3280'0000U)) { + return unlikely(x_abs == 0) ? x : (x + 0x1.5555555555555p-2 * x * x * x); + } + + double xdbl = x; + double x2 = xdbl * xdbl; + // Pure Taylor series. + double pe = fputil::polyeval(x2, 0.0, 0x1.5555555555555p-2, + 0x1.999999999999ap-3, 0x1.2492492492492p-3, + 0x1.c71c71c71c71cp-4, 0x1.745d1745d1746p-4); + return fputil::multiply_add(xdbl, pe, xdbl); + } + double xdbl = x; + return 0.5 * log_eval((xdbl + 1.0) / (xdbl - 1.0)); +} + +} // namespace __llvm_libc diff --git a/libc/test/src/math/CMakeLists.txt b/libc/test/src/math/CMakeLists.txt --- a/libc/test/src/math/CMakeLists.txt +++ b/libc/test/src/math/CMakeLists.txt @@ -1374,6 +1374,18 @@ libc.src.__support.FPUtil.fputil ) +add_fp_unittest( + atanhf_test + NEED_MPFR + SUITE + libc_math_unittests + SRCS + atanhf_test.cpp + DEPENDS + libc.src.math.atanhf + libc.src.__support.FPUtil.fputil +) + add_subdirectory(generic) add_subdirectory(exhaustive) add_subdirectory(differential_testing) diff --git a/libc/test/src/math/atanhf_test.cpp b/libc/test/src/math/atanhf_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/src/math/atanhf_test.cpp @@ -0,0 +1,108 @@ +//===-- Unittests for atanhf ----------------------------------------------===// +// +// 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/FPUtil/FPBits.h" +#include "src/math/atanhf.h" +#include "utils/MPFRWrapper/MPFRUtils.h" +#include "utils/UnitTest/FPMatcher.h" +#include "utils/UnitTest/Test.h" +#include + +#include +#include + +using FPBits = __llvm_libc::fputil::FPBits; + +namespace mpfr = __llvm_libc::testing::mpfr; + +DECLARE_SPECIAL_CONSTANTS(float) + +TEST(LlvmLibcAtanhfTest, SpecialNumbers) { + errno = 0; + __llvm_libc::fputil::clear_except(FE_ALL_EXCEPT); + EXPECT_FP_EQ(aNaN, __llvm_libc::atanhf(aNaN)); + EXPECT_FP_EXCEPTION(FE_INVALID); + EXPECT_MATH_ERRNO(0); + + __llvm_libc::fputil::clear_except(FE_ALL_EXCEPT); + EXPECT_FP_EQ(0.0f, __llvm_libc::atanhf(0.0f)); + EXPECT_FP_EXCEPTION(0); + EXPECT_MATH_ERRNO(0); + + __llvm_libc::fputil::clear_except(FE_ALL_EXCEPT); + EXPECT_FP_EQ(-0.0f, __llvm_libc::atanhf(-0.0f)); + EXPECT_FP_EXCEPTION(0); + EXPECT_MATH_ERRNO(0); + + __llvm_libc::fputil::clear_except(FE_ALL_EXCEPT); + EXPECT_FP_EQ(inf, __llvm_libc::atanhf(1.0f)); + EXPECT_FP_EXCEPTION(FE_DIVBYZERO); + EXPECT_MATH_ERRNO(ERANGE); + + __llvm_libc::fputil::clear_except(FE_ALL_EXCEPT); + EXPECT_FP_EQ(neg_inf, __llvm_libc::atanhf(-1.0f)); + EXPECT_FP_EXCEPTION(FE_DIVBYZERO); + EXPECT_MATH_ERRNO(ERANGE); + + auto bt = FPBits(1.0f); + bt.bits += 1; + + __llvm_libc::fputil::clear_except(FE_ALL_EXCEPT); + EXPECT_FP_EQ(aNaN, __llvm_libc::atanhf(bt.get_val())); + EXPECT_FP_EXCEPTION(FE_INVALID); + EXPECT_MATH_ERRNO(EDOM); + + __llvm_libc::fputil::clear_except(FE_ALL_EXCEPT); + bt.set_sign(true); + EXPECT_FP_EQ(aNaN, __llvm_libc::atanhf(bt.get_val())); + EXPECT_FP_EXCEPTION(FE_INVALID); + EXPECT_MATH_ERRNO(EDOM); + + __llvm_libc::fputil::clear_except(FE_ALL_EXCEPT); + EXPECT_FP_EQ(aNaN, __llvm_libc::atanhf(2.0f)); + EXPECT_FP_EXCEPTION(FE_INVALID); + EXPECT_MATH_ERRNO(EDOM); + + __llvm_libc::fputil::clear_except(FE_ALL_EXCEPT); + EXPECT_FP_EQ(aNaN, __llvm_libc::atanhf(-2.0f)); + EXPECT_FP_EXCEPTION(FE_INVALID); + EXPECT_MATH_ERRNO(EDOM); + + __llvm_libc::fputil::clear_except(FE_ALL_EXCEPT); + EXPECT_FP_EQ(aNaN, __llvm_libc::atanhf(inf)); + EXPECT_FP_EXCEPTION(FE_INVALID); + EXPECT_MATH_ERRNO(EDOM); + + bt.set_sign(true); + EXPECT_FP_EQ(aNaN, __llvm_libc::atanhf(neg_inf)); + EXPECT_FP_EXCEPTION(FE_INVALID); + EXPECT_MATH_ERRNO(EDOM); +} + +TEST(LlvmLibcAtanhfTest, InFloatRange) { + constexpr uint32_t COUNT = 1000000; + const uint32_t STEP = FPBits(1.0f).uintval() / COUNT; + for (uint32_t i = 0, v = 0; i <= COUNT; ++i, v += STEP) { + float x = float(FPBits(v)); + ASSERT_MPFR_MATCH(mpfr::Operation::Atanh, x, __llvm_libc::atanhf(x), 0.5); + ASSERT_MPFR_MATCH(mpfr::Operation::Atanh, -x, __llvm_libc::atanhf(-x), 0.5); + } +} + +// For small values, atanh(x) is x. +TEST(LlvmLibcAtanhfTest, SmallValues) { + float x = float(FPBits(uint32_t(0x17800000))); + float result = __llvm_libc::atanhf(x); + EXPECT_MPFR_MATCH(mpfr::Operation::Atanh, x, result, 0.5); + EXPECT_FP_EQ(x, result); + + x = float(FPBits(uint32_t(0x00400000))); + result = __llvm_libc::atanhf(x); + EXPECT_MPFR_MATCH(mpfr::Operation::Atanh, x, result, 0.5); + EXPECT_FP_EQ(x, result); +} diff --git a/libc/test/src/math/exhaustive/CMakeLists.txt b/libc/test/src/math/exhaustive/CMakeLists.txt --- a/libc/test/src/math/exhaustive/CMakeLists.txt +++ b/libc/test/src/math/exhaustive/CMakeLists.txt @@ -291,3 +291,19 @@ -lpthread ) +add_fp_unittest( + atanhf_test + NO_RUN_POSTBUILD + NEED_MPFR + SUITE + libc_math_exhaustive_tests + SRCS + atanhf_test.cpp + DEPENDS + .exhaustive_test + libc.include.math + libc.src.math.atanhf + libc.src.__support.FPUtil.fputil + LINK_LIBRARIES + -lpthread +) diff --git a/libc/test/src/math/exhaustive/atanhf_test.cpp b/libc/test/src/math/exhaustive/atanhf_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/src/math/exhaustive/atanhf_test.cpp @@ -0,0 +1,76 @@ +//===-- Exhaustive test for atanhf ----------------------------------------===// +// +// 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 "exhaustive_test.h" +#include "src/__support/FPUtil/FPBits.h" +#include "src/math/atanhf.h" +#include "utils/MPFRWrapper/MPFRUtils.h" + +#include + +using FPBits = __llvm_libc::fputil::FPBits; + +namespace mpfr = __llvm_libc::testing::mpfr; + +struct LlvmLibcAtanhfExhaustiveTest : public LlvmLibcExhaustiveTest { + bool check(uint32_t start, uint32_t stop, + mpfr::RoundingMode rounding) override { + mpfr::ForceRoundingMode r(rounding); + uint32_t bits = start; + bool result = true; + do { + FPBits xbits(bits); + float x = float(xbits); + result &= EXPECT_MPFR_MATCH(mpfr::Operation::Atanh, x, + __llvm_libc::atanhf(x), 0.5, rounding); + } while (bits++ < stop); + return result; + } +}; + +static const int NUM_THREADS = std::thread::hardware_concurrency(); + +// Range: [0, 1.0]; +static const uint32_t POS_START = 0x0000'0000U; +static const uint32_t POS_STOP = FPBits(1.0f).uintval(); + +TEST_F(LlvmLibcAtanhfExhaustiveTest, PostiveRangeRoundNearestTieToEven) { + test_full_range(POS_START, POS_STOP, mpfr::RoundingMode::Nearest); +} + +TEST_F(LlvmLibcAtanhfExhaustiveTest, PostiveRangeRoundUp) { + test_full_range(POS_START, POS_STOP, mpfr::RoundingMode::Upward); +} + +TEST_F(LlvmLibcAtanhfExhaustiveTest, PostiveRangeRoundDown) { + test_full_range(POS_START, POS_STOP, mpfr::RoundingMode::Downward); +} + +TEST_F(LlvmLibcAtanhfExhaustiveTest, PostiveRangeRoundTowardZero) { + test_full_range(POS_START, POS_STOP, mpfr::RoundingMode::TowardZero); +} + +// Range: [-1.0, 0]; +static const uint32_t NEG_START = 0x8000'0000U; +static const uint32_t NEG_STOP = FPBits(-1.0f).uintval(); + +TEST_F(LlvmLibcAtanhfExhaustiveTest, NegativeRangeRoundNearestTieToEven) { + test_full_range(NEG_START, NEG_STOP, mpfr::RoundingMode::Nearest); +} + +TEST_F(LlvmLibcAtanhfExhaustiveTest, NegativeRangeRoundUp) { + test_full_range(NEG_START, NEG_STOP, mpfr::RoundingMode::Upward); +} + +TEST_F(LlvmLibcAtanhfExhaustiveTest, NegativeRangeRoundDown) { + test_full_range(NEG_START, NEG_STOP, mpfr::RoundingMode::Downward); +} + +TEST_F(LlvmLibcAtanhfExhaustiveTest, NegativeRangeRoundTowardZero) { + test_full_range(NEG_START, NEG_STOP, mpfr::RoundingMode::TowardZero); +} diff --git a/libc/utils/MPFRWrapper/MPFRUtils.h b/libc/utils/MPFRWrapper/MPFRUtils.h --- a/libc/utils/MPFRWrapper/MPFRUtils.h +++ b/libc/utils/MPFRWrapper/MPFRUtils.h @@ -25,6 +25,7 @@ // and output floating point numbers are of the same kind. BeginUnaryOperationsSingleOutput, Abs, + Atanh, Ceil, Cos, Cosh, diff --git a/libc/utils/MPFRWrapper/MPFRUtils.cpp b/libc/utils/MPFRWrapper/MPFRUtils.cpp --- a/libc/utils/MPFRWrapper/MPFRUtils.cpp +++ b/libc/utils/MPFRWrapper/MPFRUtils.cpp @@ -182,6 +182,12 @@ return result; } + MPFRNumber atanh() const { + MPFRNumber result(*this); + mpfr_atanh(result.value, value, mpfr_rounding); + return result; + } + MPFRNumber ceil() const { MPFRNumber result(*this); mpfr_ceil(result.value, value); @@ -500,6 +506,8 @@ switch (op) { case Operation::Abs: return mpfrInput.abs(); + case Operation::Atanh: + return mpfrInput.atanh(); case Operation::Ceil: return mpfrInput.ceil(); case Operation::Cos: