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 @@ -192,6 +192,7 @@ libc.src.math.sqrt libc.src.math.sqrtf libc.src.math.sqrtl + libc.src.math.tanhf libc.src.math.trunc libc.src.math.truncf libc.src.math.truncl 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 @@ -211,6 +211,7 @@ libc.src.math.sqrt libc.src.math.sqrtf libc.src.math.sqrtl + libc.src.math.tanhf libc.src.math.trunc libc.src.math.truncf libc.src.math.truncl 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 @@ -219,6 +219,7 @@ libc.src.math.sqrtf libc.src.math.sqrtl libc.src.math.tan + libc.src.math.tanhf libc.src.math.trunc libc.src.math.truncf libc.src.math.truncl 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 @@ -196,6 +196,7 @@ libc.src.math.sqrtf libc.src.math.sqrtl libc.src.math.tan + libc.src.math.tanhf libc.src.math.trunc libc.src.math.truncf libc.src.math.truncl diff --git a/libc/spec/stdc.td b/libc/spec/stdc.td --- a/libc/spec/stdc.td +++ b/libc/spec/stdc.td @@ -473,6 +473,7 @@ FunctionSpec<"coshf", RetValSpec, [ArgSpec]>, FunctionSpec<"sinhf", RetValSpec, [ArgSpec]>, + FunctionSpec<"tanhf", 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 @@ -189,6 +189,7 @@ add_math_entrypoint_object(sqrtl) add_math_entrypoint_object(tan) +add_math_entrypoint_object(tanhf) add_math_entrypoint_object(trunc) add_math_entrypoint_object(truncf) 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 @@ -1172,3 +1172,21 @@ -O3 ) +add_entrypoint_object( + tanhf + SRCS + tanhf.cpp + HDRS + ../tanhf.h + expxf.h + DEPENDS + .common_constants + 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/tanhf.cpp b/libc/src/math/generic/tanhf.cpp new file mode 100644 --- /dev/null +++ b/libc/src/math/generic/tanhf.cpp @@ -0,0 +1,62 @@ +//===-- Single-precision tanh 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/tanhf.h" +#include "src/__support/FPUtil/FPBits.h" +#include "src/math/generic/expxf.h" + +namespace __llvm_libc { + +LLVM_LIBC_FUNCTION(float, tanhf, (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| <= 2^-26 + if (unlikely(x_abs <= 0x3280'0000U)) { + return unlikely(x_abs == 0) ? x : (x - 0x1.5555555555555p-2 * x * x * x); + } + + // When |x| >= 15, or x is inf or nan + if (unlikely(x_abs >= 0x4170'0000U)) { + if (xbits.is_nan()) + return x + 1.0f; // sNaN to qNaN + signal + + if (xbits.is_inf()) + return sign ? -1.0f : 1.0f; + + if (sign) { + return -1.0f + opt_barrier(FPBits(FPBits::MIN_NORMAL).get_val()); + } else + return 1.0f - opt_barrier(FPBits(FPBits::MIN_NORMAL).get_val()); + } + + // |x| <= 0.078125 + if (unlikely(x_abs <= 0x3da0'0000U)) { + double xdbl = x; + double x2 = xdbl * xdbl; + // Pure Taylor series. + double pe = fputil::polyeval(x2, 0.0, -0x1.5555555555555p-2, + 0x1.1111111111111p-3, -0x1.ba1ba1ba1ba1cp-5, + 0x1.664f4882c10fap-6, -0x1.226e355e6c23dp-7); + return fputil::multiply_add(xdbl, pe, xdbl); + } + + if (unlikely(xbits.bits == 0x4058'e0a3U)) { + if (fputil::get_round() == FE_DOWNWARD) + return FPBits(0x3f7f'6ad9U).get_val(); + } + + auto ep = exp_eval(2.0f * (sign ? x : -x)); // exp(-2 * x) + double result = fputil::multiply_add(ep.mult_exp, ep.r, ep.mult_exp - 1.0) / + (fputil::multiply_add(ep.mult_exp, ep.r, ep.mult_exp + 1.0)); + return sign ? result : -result; +} + +} // namespace __llvm_libc diff --git a/libc/src/math/tanhf.h b/libc/src/math/tanhf.h new file mode 100644 --- /dev/null +++ b/libc/src/math/tanhf.h @@ -0,0 +1,18 @@ +//===-- Implementation header for tanhf -------------------------*- 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_TANHF_H +#define LLVM_LIBC_SRC_MATH_TANHF_H + +namespace __llvm_libc { + +float tanhf(float x); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_MATH_TANHF_H 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 @@ -1346,6 +1346,18 @@ libc.src.__support.FPUtil.fputil ) +add_fp_unittest( + tanhf_test + NEED_MPFR + SUITE + libc_math_unittests + SRCS + tanhf_test.cpp + DEPENDS + libc.src.math.tanhf + libc.src.__support.FPUtil.fputil +) + add_subdirectory(generic) add_subdirectory(exhaustive) add_subdirectory(differential_testing) 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 @@ -240,3 +240,20 @@ -lpthread ) +add_fp_unittest( + tanhf_test + NO_RUN_POSTBUILD + NEED_MPFR + SUITE + libc_math_exhaustive_tests + SRCS + tanhf_test.cpp + DEPENDS + .exhaustive_test + libc.include.math + libc.src.math.tanhf + libc.src.__support.FPUtil.fputil + LINK_LIBRARIES + -lpthread +) + diff --git a/libc/test/src/math/exhaustive/tanhf_test.cpp b/libc/test/src/math/exhaustive/tanhf_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/src/math/exhaustive/tanhf_test.cpp @@ -0,0 +1,76 @@ +//===-- Exhaustive test for tanhf -----------------------------------------===// +// +// 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/tanhf.h" +#include "utils/MPFRWrapper/MPFRUtils.h" + +#include + +using FPBits = __llvm_libc::fputil::FPBits; + +namespace mpfr = __llvm_libc::testing::mpfr; + +struct LlvmLibcTanhfExhaustiveTest : 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::Tanh, x, + __llvm_libc::tanhf(x), 0.5, rounding); + } while (bits++ < stop); + return result; + } +}; + +static const int NUM_THREADS = std::thread::hardware_concurrency(); + +// Range: [0, INF]; +static const uint32_t POS_START = 0x0000'0000U; +static const uint32_t POS_STOP = FPBits::inf(false).uintval(); + +TEST_F(LlvmLibcTanhfExhaustiveTest, PostiveRangeRoundNearestTieToEven) { + test_full_range(POS_START, POS_STOP, mpfr::RoundingMode::Nearest); +} + +TEST_F(LlvmLibcTanhfExhaustiveTest, PostiveRangeRoundUp) { + test_full_range(POS_START, POS_STOP, mpfr::RoundingMode::Upward); +} + +TEST_F(LlvmLibcTanhfExhaustiveTest, PostiveRangeRoundDown) { + test_full_range(POS_START, POS_STOP, mpfr::RoundingMode::Downward); +} + +TEST_F(LlvmLibcTanhfExhaustiveTest, PostiveRangeRoundTowardZero) { + test_full_range(POS_START, POS_STOP, mpfr::RoundingMode::TowardZero); +} + +// Range: [-INF, 0]; +static const uint32_t NEG_START = 0x8000'0000U; +static const uint32_t NEG_STOP = FPBits::inf(true).uintval(); + +TEST_F(LlvmLibcTanhfExhaustiveTest, NegativeRangeRoundNearestTieToEven) { + test_full_range(NEG_START, NEG_STOP, mpfr::RoundingMode::Nearest); +} + +TEST_F(LlvmLibcTanhfExhaustiveTest, NegativeRangeRoundUp) { + test_full_range(NEG_START, NEG_STOP, mpfr::RoundingMode::Upward); +} + +TEST_F(LlvmLibcTanhfExhaustiveTest, NegativeRangeRoundDown) { + test_full_range(NEG_START, NEG_STOP, mpfr::RoundingMode::Downward); +} + +TEST_F(LlvmLibcTanhfExhaustiveTest, NegativeRangeRoundTowardZero) { + test_full_range(NEG_START, NEG_STOP, mpfr::RoundingMode::TowardZero); +} diff --git a/libc/test/src/math/tanhf_test.cpp b/libc/test/src/math/tanhf_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/src/math/tanhf_test.cpp @@ -0,0 +1,77 @@ +//===-- Unittests for tanhf -----------------------------------------------===// +// +// 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/CPP/Array.h" +#include "src/__support/FPUtil/FPBits.h" +#include "src/math/tanhf.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(LlvmLibcTanhfTest, SpecialNumbers) { + errno = 0; + + EXPECT_FP_EQ(aNaN, __llvm_libc::tanhf(aNaN)); + EXPECT_MATH_ERRNO(0); + + EXPECT_FP_EQ(0.0f, __llvm_libc::tanhf(0.0f)); + EXPECT_MATH_ERRNO(0); + + EXPECT_FP_EQ(-0.0f, __llvm_libc::tanhf(-0.0f)); + EXPECT_MATH_ERRNO(0); + + EXPECT_FP_EQ(1.0f, __llvm_libc::tanhf(inf)); + EXPECT_MATH_ERRNO(0); + + EXPECT_FP_EQ(-1.0f, __llvm_libc::tanhf(neg_inf)); + EXPECT_MATH_ERRNO(0); +} + +TEST(LlvmLibcTanhfTest, InFloatRange) { + constexpr uint32_t COUNT = 1000000; + constexpr uint32_t STEP = UINT32_MAX / COUNT; + for (uint32_t i = 0, v = 0; i <= COUNT; ++i, v += STEP) { + float x = float(FPBits(v)); + if (isnan(x) || isinf(x)) + continue; + ASSERT_MPFR_MATCH(mpfr::Operation::Tanh, x, __llvm_libc::tanhf(x), 0.5); + } +} + +// For small values, tanh(x) is x. +TEST(LlvmLibcTanhfTest, SmallValues) { + float x = float(FPBits(uint32_t(0x17800000))); + float result = __llvm_libc::tanhf(x); + EXPECT_MPFR_MATCH(mpfr::Operation::Tanh, x, result, 0.5); + EXPECT_FP_EQ(x, result); + + x = float(FPBits(uint32_t(0x00400000))); + result = __llvm_libc::tanhf(x); + EXPECT_MPFR_MATCH(mpfr::Operation::Tanh, x, result, 0.5); + EXPECT_FP_EQ(x, result); +} + +TEST(LlvmLibcTanhfTest, ExceptionalValues) { + float x = float(FPBits(uint32_t(0x3a12'85ffU))); + EXPECT_MPFR_MATCH_ALL_ROUNDING(mpfr::Operation::Tanh, x, + __llvm_libc::tanhf(x), 0.5); + + x = -float(FPBits(uint32_t(0x3a12'85ffU))); + EXPECT_MPFR_MATCH_ALL_ROUNDING(mpfr::Operation::Tanh, x, + __llvm_libc::tanhf(x), 0.5); +} 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 @@ -44,6 +44,7 @@ Sinh, Sqrt, Tan, + Tanh, Trunc, EndUnaryOperationsSingleOutput, 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 @@ -355,6 +355,12 @@ return result; } + MPFRNumber tanh() const { + MPFRNumber result(*this); + mpfr_tanh(result.value, value, mpfr_rounding); + return result; + } + MPFRNumber trunc() const { MPFRNumber result(*this); mpfr_trunc(result.value, value); @@ -527,6 +533,8 @@ return mpfrInput.sqrt(); case Operation::Tan: return mpfrInput.tan(); + case Operation::Tanh: + return mpfrInput.tanh(); case Operation::Trunc: return mpfrInput.trunc(); default: