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 @@ -110,6 +110,7 @@ # math.h entrypoints libc.src.math.acosf + libc.src.math.acoshf libc.src.math.asinf libc.src.math.asinhf libc.src.math.atanf 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 @@ -210,6 +210,7 @@ # math.h entrypoints libc.src.math.acosf + libc.src.math.acoshf libc.src.math.asin libc.src.math.asinf libc.src.math.asinhf 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 @@ -211,6 +211,7 @@ # math.h entrypoints libc.src.math.acosf + libc.src.math.acoshf libc.src.math.asin libc.src.math.asinf libc.src.math.asinhf 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 @@ -111,6 +111,7 @@ # math.h entrypoints libc.src.math.acosf + libc.src.math.acoshf libc.src.math.asin libc.src.math.asinf libc.src.math.asinhf diff --git a/libc/docs/math.rst b/libc/docs/math.rst --- a/libc/docs/math.rst +++ b/libc/docs/math.rst @@ -122,7 +122,7 @@ (float) (double) (long double) ============== ================ =============== ====================== acos :green:`XA` -acosh +acosh :green:`XA` asin :green:`XA` asinh :green:`XA` atan :green:`XA` @@ -161,6 +161,7 @@ (float) (double) (long double) ============== ================ =============== ====================== acos :green:`XA` +acosh :green:`XA` asin :green:`XA` asinh :green:`XA` atan :green:`XA` diff --git a/libc/spec/stdc.td b/libc/spec/stdc.td --- a/libc/spec/stdc.td +++ b/libc/spec/stdc.td @@ -492,6 +492,7 @@ FunctionSpec<"asin", RetValSpec, [ArgSpec]>, FunctionSpec<"atanf", RetValSpec, [ArgSpec]>, + FunctionSpec<"acoshf", RetValSpec, [ArgSpec]>, FunctionSpec<"asinhf", 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 @@ -65,6 +65,7 @@ ) add_math_entrypoint_object(acosf) +add_math_entrypoint_object(acoshf) add_math_entrypoint_object(asin) add_math_entrypoint_object(asinf) diff --git a/libc/src/math/acoshf.h b/libc/src/math/acoshf.h new file mode 100644 --- /dev/null +++ b/libc/src/math/acoshf.h @@ -0,0 +1,18 @@ +//===-- Implementation header for acoshf ------------------------*- 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_ACOSHF_H +#define LLVM_LIBC_SRC_MATH_ACOSHF_H + +namespace __llvm_libc { + +float acoshf(float x); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_MATH_ACOSHF_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 @@ -1296,6 +1296,23 @@ -O3 ) +add_entrypoint_object( + acoshf + SRCS + acoshf.cpp + HDRS + ../acoshf.h + DEPENDS + .explogxf + 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.sqrt + COMPILE_OPTIONS + -O3 +) + add_entrypoint_object( asinhf SRCS diff --git a/libc/src/math/generic/acoshf.cpp b/libc/src/math/generic/acoshf.cpp new file mode 100644 --- /dev/null +++ b/libc/src/math/generic/acoshf.cpp @@ -0,0 +1,72 @@ +//===-- Single-precision acosh 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/acoshf.h" +#include "src/__support/FPUtil/FEnvImpl.h" +#include "src/__support/FPUtil/FPBits.h" +#include "src/__support/FPUtil/PolyEval.h" +#include "src/__support/FPUtil/multiply_add.h" +#include "src/__support/FPUtil/sqrt.h" +#include "src/math/generic/common_constants.h" +#include "src/math/generic/explogxf.h" + +namespace __llvm_libc { + +LLVM_LIBC_FUNCTION(float, acoshf, (float x)) { + using FPBits_t = typename fputil::FPBits; + FPBits_t xbits(x); + uint32_t x_u = xbits.uintval(); + + if (unlikely(x < 1.0f)) { + // x < 1. + fputil::set_except(FE_INVALID); + return FPBits_t::build_quiet_nan(0); + } + + if (unlikely(x_u >= 0x4f8ffb03)) { + // Check for exceptional values. + uint32_t x_abs = x_u & FPBits_t::FloatProp::EXP_MANT_MASK; + if (unlikely(x_abs >= 0x7f80'0000U)) { + // x is +inf or NaN. + return x; + } + + // Helper functions to set results for exceptional cases. + auto round_result_slightly_down = [](float r) -> float { + volatile float tmp = r; + tmp = tmp - 0x1.0p-25f; + return tmp; + }; + auto round_result_slightly_up = [](float r) -> float { + volatile float tmp = r; + tmp = tmp + 0x1.0p-25f; + return tmp; + }; + + switch (x_u) { + case 0x4f8ffb03: // x = 0x1.1ff606p32f + return round_result_slightly_up(0x1.6fdd34p4f); + case 0x5c569e88: // x = 0x1.ad3d1p57f + return round_result_slightly_up(0x1.45c146p5f); + case 0x5e68984e: // x = 0x1.d1309cp61f + return round_result_slightly_up(0x1.5c9442p5f); + case 0x655890d3: // x = 0x1.b121a6p75f + return round_result_slightly_down(0x1.a9a3f2p5f); + case 0x6eb1a8ec: // x = 0x1.6351d8p94f + return round_result_slightly_down(0x1.08b512p6f); + case 0x7997f30a: // x = 0x1.2fe614p116f + return round_result_slightly_up(0x1.451436p6f); + } + } + + double x_d = static_cast(x); + // acosh(x) = log(x + sqrt(x^2 - 1)) + return log_eval(x_d + fputil::sqrt(fputil::multiply_add(x_d, x_d, -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 @@ -1485,6 +1485,20 @@ libc.src.__support.FPUtil.fp_bits ) +add_fp_unittest( + acoshf_test + NEED_MPFR + SUITE + libc_math_unittests + SRCS + acoshf_test.cpp + DEPENDS + libc.include.errno + libc.src.errno.errno + libc.src.math.acoshf + libc.src.__support.FPUtil.fp_bits +) + add_fp_unittest( asinf_test NEED_MPFR diff --git a/libc/test/src/math/acoshf_test.cpp b/libc/test/src/math/acoshf_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/src/math/acoshf_test.cpp @@ -0,0 +1,78 @@ +//===-- Unittests for acoshf ----------------------------------------------===// +// +// 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/acoshf.h" +#include "utils/MPFRWrapper/MPFRUtils.h" +#include "utils/UnitTest/FPMatcher.h" +#include "utils/UnitTest/Test.h" +#include + +#include +#include + +using FPBits_t = __llvm_libc::fputil::FPBits; + +namespace mpfr = __llvm_libc::testing::mpfr; + +DECLARE_SPECIAL_CONSTANTS(float) + +TEST(LlvmLibcAcoshfTest, SpecialNumbers) { + errno = 0; + + EXPECT_FP_EQ(aNaN, __llvm_libc::acoshf(aNaN)); + EXPECT_MATH_ERRNO(0); + + EXPECT_FP_EQ(aNaN, __llvm_libc::acoshf(0.0f)); + EXPECT_MATH_ERRNO(0); + + EXPECT_FP_EQ(0.0f, __llvm_libc::acoshf(1.0f)); + EXPECT_MATH_ERRNO(0); + + EXPECT_FP_EQ(inf, __llvm_libc::acoshf(inf)); + EXPECT_MATH_ERRNO(0); + + EXPECT_FP_EQ(aNaN, __llvm_libc::acoshf(neg_inf)); + EXPECT_MATH_ERRNO(0); +} + +TEST(LlvmLibcAcoshfTest, 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_t(v)); + if (isnan(x) || isinf(x)) + continue; + ASSERT_MPFR_MATCH_ALL_ROUNDING(mpfr::Operation::Acosh, x, + __llvm_libc::acoshf(x), 0.5); + } +} + +TEST(LlvmLibcAcoshfTest, SpecificBitPatterns) { + constexpr int N = 12; + constexpr uint32_t INPUTS[N] = { + 0x3f800000, // x = 1.0f + 0x45abaf26, // x = 0x1.575e4cp12f + 0x49d29048, // x = 0x1.a5209p20f + 0x4bdd65a5, // x = 0x1.bacb4ap24f + 0x4c803f2c, // x = 0x1.007e58p26f + 0x4f8ffb03, // x = 0x1.1ff606p32f + 0x5c569e88, // x = 0x1.ad3d1p57f + 0x5e68984e, // x = 0x1.d1309cp61f + 0x655890d3, // x = 0x1.b121a6p75f + 0x65de7ca6, // x = 0x1.bcf94cp76f + 0x6eb1a8ec, // x = 0x1.6351d8p94f + 0x7997f30a, // x = 0x1.2fe614p116f + }; + + for (int i = 0; i < N; ++i) { + float x = float(FPBits_t(INPUTS[i])); + EXPECT_MPFR_MATCH_ALL_ROUNDING(mpfr::Operation::Acosh, x, + __llvm_libc::acoshf(x), 0.5); + } +} 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 @@ -308,6 +308,23 @@ -lpthread ) +add_fp_unittest( + acoshf_test + NO_RUN_POSTBUILD + NEED_MPFR + SUITE + libc_math_exhaustive_tests + SRCS + acoshf_test.cpp + DEPENDS + .exhaustive_test + libc.include.math + libc.src.math.acoshf + libc.src.__support.FPUtil.fp_bits + LINK_LIBRARIES + -lpthread +) + add_fp_unittest( asinhf_test NO_RUN_POSTBUILD diff --git a/libc/test/src/math/exhaustive/acoshf_test.cpp b/libc/test/src/math/exhaustive/acoshf_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/src/math/exhaustive/acoshf_test.cpp @@ -0,0 +1,56 @@ +//===-- Exhaustive test for acoshf ----------------------------------------===// +// +// 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/acoshf.h" +#include "utils/MPFRWrapper/MPFRUtils.h" + +#include + +using FPBits = __llvm_libc::fputil::FPBits; + +namespace mpfr = __llvm_libc::testing::mpfr; + +struct LlvmLibcAcoshfExhaustiveTest : 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::Acosh, x, + __llvm_libc::acoshf(x), 0.5, rounding); + } while (bits++ < stop); + return result; + } +}; + +static const int NUM_THREADS = std::thread::hardware_concurrency(); + +// Range: [1, Inf]; +static const uint32_t POS_START = 0x3f80'0000U; +static const uint32_t POS_STOP = 0x7f80'0000U; + +TEST_F(LlvmLibcAcoshfExhaustiveTest, PostiveRangeRoundNearestTieToEven) { + test_full_range(POS_START, POS_STOP, mpfr::RoundingMode::Nearest); +} + +TEST_F(LlvmLibcAcoshfExhaustiveTest, PostiveRangeRoundUp) { + test_full_range(POS_START, POS_STOP, mpfr::RoundingMode::Upward); +} + +TEST_F(LlvmLibcAcoshfExhaustiveTest, PostiveRangeRoundDown) { + test_full_range(POS_START, POS_STOP, mpfr::RoundingMode::Downward); +} + +TEST_F(LlvmLibcAcoshfExhaustiveTest, 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 @@ -26,6 +26,7 @@ BeginUnaryOperationsSingleOutput, Abs, Acos, + Acosh, Asin, Asinh, Atan, 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 @@ -190,6 +190,12 @@ return result; } + MPFRNumber acosh() const { + MPFRNumber result(*this); + mpfr_acosh(result.value, value, mpfr_rounding); + return result; + } + MPFRNumber asin() const { MPFRNumber result(*this); mpfr_asin(result.value, value, mpfr_rounding); @@ -201,6 +207,7 @@ mpfr_asinh(result.value, value, mpfr_rounding); return result; } + MPFRNumber atan() const { MPFRNumber result(*this); mpfr_atan(result.value, value, mpfr_rounding); @@ -545,6 +552,8 @@ return mpfrInput.abs(); case Operation::Acos: return mpfrInput.acos(); + case Operation::Acosh: + return mpfrInput.acosh(); case Operation::Asin: return mpfrInput.asin(); case Operation::Asinh: