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 @@ -111,6 +111,7 @@ libc.src.math.ceil libc.src.math.ceilf libc.src.math.ceill + libc.src.math.coshf libc.src.math.cosf libc.src.math.expf libc.src.math.exp2f 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 @@ -130,6 +130,7 @@ libc.src.math.ceil libc.src.math.ceilf libc.src.math.ceill + libc.src.math.coshf libc.src.math.cosf libc.src.math.expf libc.src.math.exp2f 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 @@ -136,6 +136,7 @@ libc.src.math.ceilf libc.src.math.ceill libc.src.math.cos + libc.src.math.coshf libc.src.math.cosf libc.src.math.expf libc.src.math.exp2f 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 @@ -114,6 +114,7 @@ libc.src.math.ceill libc.src.math.cos libc.src.math.cosf + libc.src.math.coshf libc.src.math.expf libc.src.math.exp2f libc.src.math.expm1f diff --git a/libc/spec/stdc.td b/libc/spec/stdc.td --- a/libc/spec/stdc.td +++ b/libc/spec/stdc.td @@ -470,6 +470,8 @@ FunctionSpec<"nextafterf", RetValSpec, [ArgSpec, ArgSpec]>, FunctionSpec<"nextafter", RetValSpec, [ArgSpec, ArgSpec]>, FunctionSpec<"nextafterl", RetValSpec, [ArgSpec, ArgSpec]>, + + FunctionSpec<"coshf", RetValSpec, [ArgSpec]>, ] >; diff --git a/libc/src/__support/FPUtil/FPBits.h b/libc/src/__support/FPUtil/FPBits.h --- a/libc/src/__support/FPUtil/FPBits.h +++ b/libc/src/__support/FPUtil/FPBits.h @@ -153,8 +153,8 @@ static constexpr FPBits neg_zero() { return zero(true); } - static constexpr FPBits inf() { - FPBits bits; + static constexpr FPBits inf(bool sign = false) { + FPBits bits(sign ? FloatProp::SIGN_MASK : UIntType(0)); bits.set_unbiased_exponent(MAX_EXPONENT); return bits; } 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 @@ -76,6 +76,7 @@ add_math_entrypoint_object(cos) add_math_entrypoint_object(cosf) +add_math_entrypoint_object(coshf) add_math_entrypoint_object(expf) diff --git a/libc/src/math/coshf.h b/libc/src/math/coshf.h new file mode 100644 --- /dev/null +++ b/libc/src/math/coshf.h @@ -0,0 +1,18 @@ +//===-- Implementation header for coshf -------------------------*- 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_COSHF_H +#define LLVM_LIBC_SRC_MATH_COSHF_H + +namespace __llvm_libc { + +float coshf(float x); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_MATH_COSHF_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 @@ -1128,3 +1128,22 @@ COMPILE_OPTIONS -O3 ) + +add_entrypoint_object( + coshf + SRCS + coshf.cpp + HDRS + ../coshf.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/coshf.cpp b/libc/src/math/generic/coshf.cpp new file mode 100644 --- /dev/null +++ b/libc/src/math/generic/coshf.cpp @@ -0,0 +1,54 @@ +//===-- Single-precision cosh 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/coshf.h" +#include "src/__support/FPUtil/FPBits.h" +#include "src/__support/FPUtil/multiply_add.h" +#include "src/math/generic/expxf.h" + +namespace __llvm_libc { + +LLVM_LIBC_FUNCTION(float, coshf, (float x)) { + using FPBits = typename fputil::FPBits; + FPBits xbits(x); + xbits.set_sign(false); + x = xbits.get_val(); + + uint32_t x_u = xbits.uintval(); + + // |x| <= 2^-26 + if (unlikely(x_u <= 0x3280'0000U)) { + return 1.0f + x; + } + + // When |x| >= 90, or x is inf or nan + if (unlikely(x_u >= 0x42b4'0000U)) { + if (xbits.is_inf_or_nan()) + return x + FPBits::inf().get_val(); + + int rounding = fputil::get_round(); + if (unlikely(rounding == FE_DOWNWARD || rounding == FE_TOWARDZERO)) + return FPBits(FPBits::MAX_NORMAL).get_val(); + + errno = ERANGE; + + return x + FPBits::inf().get_val(); + } + auto ep_p = exp_eval<-1>(x); + auto ep_m = exp_eval<-1>(-x); + // 0.5 * exp(x) = ep_p.mult_exp * (ep_p.r + 1) + // = ep_p.mult_exp * ep_p.r + ep_p.mult_exp + // 0.5 * exp(-x) = ep_m.mult_exp * (ep_m.r + 1) + // = ep_m.mult_exp * ep_m.r + ep_m.mult_exp + // cos(x) = 0.5 * exp(x) + 0.5 * expm1(-x) + double ep = fputil::multiply_add(ep_p.mult_exp, ep_p.r, ep_p.mult_exp) + + fputil::multiply_add(ep_m.mult_exp, ep_m.r, ep_m.mult_exp); + return ep; +} + +} // 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 @@ -1314,6 +1314,22 @@ libc.src.__support.FPUtil.fputil ) +add_fp_unittest( + coshf_test + NEED_MPFR + SUITE + libc_math_unittests + SRCS + coshf_test.cpp + HDRS + sdcomp26094.h + DEPENDS + libc.include.errno + libc.src.math.coshf + libc.src.__support.CPP.array + libc.src.__support.FPUtil.fputil +) + add_subdirectory(generic) add_subdirectory(exhaustive) add_subdirectory(differential_testing) diff --git a/libc/test/src/math/coshf_test.cpp b/libc/test/src/math/coshf_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/src/math/coshf_test.cpp @@ -0,0 +1,78 @@ +//===-- Unittests for coshf -----------------------------------------------===// +// +// 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/coshf.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(LlvmLibcCoshfTest, SpecialNumbers) { + errno = 0; + + EXPECT_FP_EQ(aNaN, __llvm_libc::coshf(aNaN)); + EXPECT_MATH_ERRNO(0); + + EXPECT_FP_EQ(inf, __llvm_libc::coshf(inf)); + EXPECT_MATH_ERRNO(0); + + EXPECT_FP_EQ(inf, __llvm_libc::coshf(neg_inf)); + EXPECT_MATH_ERRNO(0); + + EXPECT_FP_EQ(1.0f, __llvm_libc::coshf(0.0f)); + EXPECT_MATH_ERRNO(0); + + EXPECT_FP_EQ(1.0f, __llvm_libc::coshf(-0.0f)); + EXPECT_MATH_ERRNO(0); +} + +TEST(LlvmLibcCoshfTest, Overflow) { + errno = 0; + EXPECT_FP_EQ(inf, __llvm_libc::coshf(float(FPBits(0x7f7fffffU)))); + EXPECT_MATH_ERRNO(ERANGE); + + EXPECT_FP_EQ(inf, __llvm_libc::coshf(float(FPBits(0x42cffff8U)))); + EXPECT_MATH_ERRNO(ERANGE); + + EXPECT_FP_EQ(inf, __llvm_libc::coshf(float(FPBits(0x42d00008U)))); + EXPECT_MATH_ERRNO(ERANGE); +} + +TEST(LlvmLibcCoshfTest, 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::Cosh, x, __llvm_libc::coshf(x), 0.5); + } +} + +TEST(LlvmLibcCoshfTest, SmallValues) { + float x = float(FPBits(0x17800000U)); + float result = __llvm_libc::coshf(x); + EXPECT_MPFR_MATCH(mpfr::Operation::Cosh, x, result, 0.5); + EXPECT_FP_EQ(1.0f, result); + + x = float(FPBits(0x0040000U)); + result = __llvm_libc::coshf(x); + EXPECT_MPFR_MATCH(mpfr::Operation::Cosh, x, result, 0.5); + EXPECT_FP_EQ(1.0f, 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 @@ -201,3 +201,21 @@ libc.src.__support.FPUtil.fputil libc.src.__support.FPUtil.generic.fmod ) + +add_fp_unittest( + coshf_test + NO_RUN_POSTBUILD + NEED_MPFR + SUITE + libc_math_exhaustive_tests + SRCS + coshf_test.cpp + DEPENDS + .exhaustive_test + libc.include.math + libc.src.math.coshf + libc.src.__support.FPUtil.fputil + LINK_LIBRARIES + -lpthread +) + diff --git a/libc/test/src/math/exhaustive/coshf_test.cpp b/libc/test/src/math/exhaustive/coshf_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/src/math/exhaustive/coshf_test.cpp @@ -0,0 +1,55 @@ +//===-- Exhaustive test for coshf -----------------------------------------===// +// +// 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/coshf.h" +#include "utils/MPFRWrapper/MPFRUtils.h" +#include "utils/UnitTest/FPMatcher.h" + +#include + +using FPBits = __llvm_libc::fputil::FPBits; + +namespace mpfr = __llvm_libc::testing::mpfr; + +struct LlvmLibcCoshfExhaustiveTest : 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::Cosh, x, + __llvm_libc::coshf(x), 0.5, rounding); + } while (bits++ < stop); + return result; + } +}; + +// Range: [0, 90]; +static constexpr uint32_t POS_START = 0x0000'0000U; +static constexpr uint32_t POS_STOP = 0x42b4'0000U; + +TEST_F(LlvmLibcCoshfExhaustiveTest, PostiveRangeRoundNearestTieToEven) { + test_full_range(POS_START, POS_STOP, mpfr::RoundingMode::Nearest); +} + +TEST_F(LlvmLibcCoshfExhaustiveTest, PostiveRangeRoundUp) { + test_full_range(POS_START, POS_STOP, mpfr::RoundingMode::Upward); +} + +TEST_F(LlvmLibcCoshfExhaustiveTest, PostiveRangeRoundDown) { + test_full_range(POS_START, POS_STOP, mpfr::RoundingMode::Downward); +} + +TEST_F(LlvmLibcCoshfExhaustiveTest, PostiveRangeRoundTowardZero) { + test_full_range(POS_START, POS_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 @@ -27,6 +27,7 @@ Abs, Ceil, Cos, + Cosh, Exp, Exp2, Expm1, 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 @@ -194,6 +194,12 @@ return result; } + MPFRNumber cosh() const { + MPFRNumber result(*this); + mpfr_cosh(result.value, value, mpfr_rounding); + return result; + } + MPFRNumber exp() const { MPFRNumber result(*this); mpfr_exp(result.value, value, mpfr_rounding); @@ -481,6 +487,8 @@ return mpfrInput.ceil(); case Operation::Cos: return mpfrInput.cos(); + case Operation::Cosh: + return mpfrInput.cosh(); case Operation::Exp: return mpfrInput.exp(); case Operation::Exp2: