Skip to content

[libc][math] Implement double-precision acosh, asinh, atanh, cosh, sinh and tanh hyperbolic math functions#199451

Open
iamaayushrivastava wants to merge 1 commit into
llvm:mainfrom
iamaayushrivastava:libc-math-hyperbolic-double
Open

[libc][math] Implement double-precision acosh, asinh, atanh, cosh, sinh and tanh hyperbolic math functions#199451
iamaayushrivastava wants to merge 1 commit into
llvm:mainfrom
iamaayushrivastava:libc-math-hyperbolic-double

Conversation

@iamaayushrivastava

Copy link
Copy Markdown
Contributor

Add generic double-precision implementations for the six hyperbolic math functions: acosh, asinh, atanh, cosh, sinh, tanh.

@iamaayushrivastava iamaayushrivastava requested a review from a team as a code owner May 24, 2026 21:40
@llvmorg-github-actions

Copy link
Copy Markdown

@llvm/pr-subscribers-libc

Author: Aayush Shrivastava (iamaayushrivastava)

Changes

Add generic double-precision implementations for the six hyperbolic math functions: acosh, asinh, atanh, cosh, sinh, tanh.


Patch is 42.09 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/199451.diff

22 Files Affected:

  • (modified) libc/config/linux/x86_64/entrypoints.txt (+6)
  • (modified) libc/src/__support/math/CMakeLists.txt (+78)
  • (added) libc/src/__support/math/acosh.h (+67)
  • (added) libc/src/__support/math/asinh.h (+79)
  • (added) libc/src/__support/math/atanh.h (+74)
  • (added) libc/src/__support/math/cosh.h (+67)
  • (added) libc/src/__support/math/sinh.h (+88)
  • (added) libc/src/__support/math/tanh.h (+68)
  • (modified) libc/src/math/generic/CMakeLists.txt (+60)
  • (added) libc/src/math/generic/acosh.cpp (+16)
  • (added) libc/src/math/generic/asinh.cpp (+16)
  • (added) libc/src/math/generic/atanh.cpp (+16)
  • (added) libc/src/math/generic/cosh.cpp (+16)
  • (added) libc/src/math/generic/sinh.cpp (+16)
  • (added) libc/src/math/generic/tanh.cpp (+16)
  • (modified) libc/test/src/math/smoke/CMakeLists.txt (+70)
  • (added) libc/test/src/math/smoke/acosh_test.cpp (+66)
  • (added) libc/test/src/math/smoke/asinh_test.cpp (+57)
  • (added) libc/test/src/math/smoke/atanh_test.cpp (+75)
  • (added) libc/test/src/math/smoke/cosh_test.cpp (+72)
  • (added) libc/test/src/math/smoke/sinh_test.cpp (+75)
  • (added) libc/test/src/math/smoke/tanh_test.cpp (+57)
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index 5545790fecd85..16d733fdde948 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -534,10 +534,12 @@ set(TARGET_LIBM_ENTRYPOINTS
     # math.h entrypoints
     libc.src.math.acos
     libc.src.math.acosf
+    libc.src.math.acosh
     libc.src.math.acoshf
     libc.src.math.acospif
     libc.src.math.asin
     libc.src.math.asinf
+    libc.src.math.asinh
     libc.src.math.asinhf
     libc.src.math.asinpi
     libc.src.math.asinpif
@@ -545,6 +547,7 @@ set(TARGET_LIBM_ENTRYPOINTS
     libc.src.math.atan2f
     libc.src.math.atan
     libc.src.math.atanf
+    libc.src.math.atanh
     libc.src.math.atanhf
     libc.src.math.canonicalize
     libc.src.math.canonicalizef
@@ -560,6 +563,7 @@ set(TARGET_LIBM_ENTRYPOINTS
     libc.src.math.copysignl
     libc.src.math.cos
     libc.src.math.cosf
+    libc.src.math.cosh
     libc.src.math.coshf
     libc.src.math.cospif
     libc.src.math.dfmal
@@ -746,6 +750,7 @@ set(TARGET_LIBM_ENTRYPOINTS
     libc.src.math.sincos
     libc.src.math.sincosf
     libc.src.math.sinf
+    libc.src.math.sinh
     libc.src.math.sinhf
     libc.src.math.sinpif
     libc.src.math.sqrt
@@ -753,6 +758,7 @@ set(TARGET_LIBM_ENTRYPOINTS
     libc.src.math.sqrtl
     libc.src.math.tan
     libc.src.math.tanf
+    libc.src.math.tanh
     libc.src.math.tanhf
     libc.src.math.tanpif
     libc.src.math.totalorder
diff --git a/libc/src/__support/math/CMakeLists.txt b/libc/src/__support/math/CMakeLists.txt
index 9e3ec26cdc881..0b41649f298bb 100644
--- a/libc/src/__support/math/CMakeLists.txt
+++ b/libc/src/__support/math/CMakeLists.txt
@@ -54,6 +54,19 @@ add_header_library(
     libc.src.__support.macros.config
 )
 
+add_header_library(
+  acosh
+  HDRS
+    acosh.h
+  DEPENDS
+    .log
+    libc.src.__support.FPUtil.fenv_impl
+    libc.src.__support.FPUtil.fp_bits
+    libc.src.__support.FPUtil.sqrt
+    libc.src.__support.macros.config
+    libc.src.__support.macros.optimization
+)
+
 add_header_library(
   acoshf_utils
   HDRS
@@ -157,6 +170,20 @@ add_header_library(
     libc.src.__support.macros.properties.cpu_features
 )
 
+add_header_library(
+  asinh
+  HDRS
+    asinh.h
+  DEPENDS
+    .log
+    .log1p
+    libc.src.__support.FPUtil.fenv_impl
+    libc.src.__support.FPUtil.fp_bits
+    libc.src.__support.FPUtil.sqrt
+    libc.src.__support.macros.config
+    libc.src.__support.macros.optimization
+)
+
 add_header_library(
   asinhf
   HDRS
@@ -354,6 +381,18 @@ add_header_library(
     libc.src.__support.macros.optimization
 )
 
+add_header_library(
+  atanh
+  HDRS
+    atanh.h
+  DEPENDS
+    .log1p
+    libc.src.__support.FPUtil.fenv_impl
+    libc.src.__support.FPUtil.fp_bits
+    libc.src.__support.macros.config
+    libc.src.__support.macros.optimization
+)
+
 add_header_library(
   atanhf
   HDRS
@@ -2797,6 +2836,19 @@ add_header_library(
     libc.src.__support.macros.properties.types
 )
 
+add_header_library(
+  cosh
+  HDRS
+    cosh.h
+  DEPENDS
+    .exp
+    libc.src.__support.FPUtil.fenv_impl
+    libc.src.__support.FPUtil.fp_bits
+    libc.src.__support.FPUtil.rounding_mode
+    libc.src.__support.macros.config
+    libc.src.__support.macros.optimization
+)
+
 add_header_library(
   coshf
   HDRS
@@ -5221,6 +5273,20 @@ add_header_library(
     libc.src.__support.macros.properties.types
 )
 
+add_header_library(
+  sinh
+  HDRS
+    sinh.h
+  DEPENDS
+    .exp
+    .expm1
+    libc.src.__support.FPUtil.fenv_impl
+    libc.src.__support.FPUtil.fp_bits
+    libc.src.__support.FPUtil.rounding_mode
+    libc.src.__support.macros.config
+    libc.src.__support.macros.optimization
+)
+
 add_header_library(
   sinhf
   HDRS
@@ -5371,6 +5437,18 @@ add_header_library(
     libc.include.llvm-libc-macros.float16_macros
 )
 
+add_header_library(
+  tanh
+  HDRS
+    tanh.h
+  DEPENDS
+    .expm1
+    libc.src.__support.FPUtil.fenv_impl
+    libc.src.__support.FPUtil.fp_bits
+    libc.src.__support.macros.config
+    libc.src.__support.macros.optimization
+)
+
 add_header_library(
   tanhf
   HDRS
diff --git a/libc/src/__support/math/acosh.h b/libc/src/__support/math/acosh.h
new file mode 100644
index 0000000000000..a3485558d8580
--- /dev/null
+++ b/libc/src/__support/math/acosh.h
@@ -0,0 +1,67 @@
+//===-- Implementation header for acosh -------------------------*- 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___SUPPORT_MATH_ACOSH_H
+#define LLVM_LIBC_SRC___SUPPORT_MATH_ACOSH_H
+
+#include "log.h"
+#include "src/__support/FPUtil/FEnvImpl.h"
+#include "src/__support/FPUtil/FPBits.h"
+#include "src/__support/FPUtil/sqrt.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/macros/optimization.h" // LIBC_UNLIKELY
+
+namespace LIBC_NAMESPACE_DECL {
+
+namespace math {
+
+LIBC_INLINE double acosh(double x) {
+  using FPBits = fputil::FPBits<double>;
+  FPBits xbits(x);
+  uint64_t x_u = xbits.uintval();
+
+  // Handle NaN.
+  if (LIBC_UNLIKELY(xbits.is_nan())) {
+    if (xbits.is_signaling_nan()) {
+      fputil::raise_except_if_required(FE_INVALID);
+      return FPBits::quiet_nan().get_val();
+    }
+    return x;
+  }
+
+  // acosh(+inf) = +inf.
+  if (LIBC_UNLIKELY(xbits.is_inf()))
+    return x;
+
+  // Domain error: acosh(x) is undefined for x < 1.
+  if (LIBC_UNLIKELY(x_u < 0x3ff0000000000000ULL)) {
+    fputil::set_errno_if_required(EDOM);
+    fputil::raise_except_if_required(FE_INVALID);
+    return FPBits::quiet_nan().get_val();
+  }
+
+  // acosh(1) = 0.
+  if (LIBC_UNLIKELY(x_u == 0x3ff0000000000000ULL))
+    return 0.0;
+
+  // For large x (x >= 2^28): acosh(x) ~ log(2x) = log(x) + log(2).
+  if (LIBC_UNLIKELY(x_u >= 0x41b0000000000000ULL)) {
+    constexpr double LOG_2 = 0x1.62e42fefa39efp-1;
+    return math::log(x) + LOG_2;
+  }
+
+  // General case: acosh(x) = log(x + sqrt(x^2 - 1)).
+  double x2m1 = x * x - 1.0;
+  return math::log(x + fputil::sqrt<double>(x2m1));
+}
+
+} // namespace math
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC___SUPPORT_MATH_ACOSH_H
diff --git a/libc/src/__support/math/asinh.h b/libc/src/__support/math/asinh.h
new file mode 100644
index 0000000000000..7e2884b80acad
--- /dev/null
+++ b/libc/src/__support/math/asinh.h
@@ -0,0 +1,79 @@
+//===-- Implementation header for asinh -------------------------*- 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___SUPPORT_MATH_ASINH_H
+#define LLVM_LIBC_SRC___SUPPORT_MATH_ASINH_H
+
+#include "log.h"
+#include "log1p.h"
+#include "src/__support/FPUtil/FEnvImpl.h"
+#include "src/__support/FPUtil/FPBits.h"
+#include "src/__support/FPUtil/sqrt.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/macros/optimization.h" // LIBC_UNLIKELY
+
+namespace LIBC_NAMESPACE_DECL {
+
+namespace math {
+
+LIBC_INLINE double asinh(double x) {
+  using FPBits = fputil::FPBits<double>;
+  FPBits xbits(x);
+
+  // Handle NaN.
+  if (LIBC_UNLIKELY(xbits.is_nan())) {
+    if (xbits.is_signaling_nan()) {
+      fputil::raise_except_if_required(FE_INVALID);
+      return FPBits::quiet_nan().get_val();
+    }
+    return x;
+  }
+
+  // Handle +/-inf: asinh(+/-inf) = +/-inf.
+  if (LIBC_UNLIKELY(xbits.is_inf()))
+    return x;
+
+  uint64_t x_abs_u = xbits.abs().uintval();
+  double x_abs = xbits.abs().get_val();
+  bool is_neg = xbits.is_neg();
+
+  // For very small |x| (|x| <= 2^-26), asinh(x) ~ x.
+  // Use FP arithmetic to ensure FTZ/DAZ mode behavior.
+  if (LIBC_UNLIKELY(x_abs_u <= 0x3e50000000000000ULL)) {
+    // asinh(+/-0) = +/-0, preserve sign of zero exactly.
+    if (LIBC_UNLIKELY(x_abs_u == 0))
+      return x;
+    double x2 = x_abs * x_abs;
+    double result = x_abs - x2 * x_abs * (1.0 / 6.0);
+    return is_neg ? -result : result;
+  }
+
+  double result;
+  // For large |x| (|x| >= 2^28): asinh(x) ~ log(2|x|) = log(|x|) + log(2).
+  if (LIBC_UNLIKELY(x_abs_u >= 0x41b0000000000000ULL)) {
+    constexpr double LOG_2 = 0x1.62e42fefa39efp-1;
+    result = math::log(x_abs) + LOG_2;
+  } else if (x_abs_u >= 0x3fe0000000000000ULL) {
+    // |x| >= 0.5: asinh(x) = log(x + sqrt(x^2 + 1)).
+    result = math::log(x_abs + fputil::sqrt<double>(x_abs * x_abs + 1.0));
+  } else {
+    // |x| < 0.5: use log1p for better accuracy near 0.
+    // asinh(x) = log1p(x + x^2 / (1 + sqrt(1 + x^2)))
+    double x2 = x_abs * x_abs;
+    double sqrt1px2 = fputil::sqrt<double>(1.0 + x2);
+    result = math::log1p(x_abs + x2 / (1.0 + sqrt1px2));
+  }
+
+  return is_neg ? -result : result;
+}
+
+} // namespace math
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC___SUPPORT_MATH_ASINH_H
diff --git a/libc/src/__support/math/atanh.h b/libc/src/__support/math/atanh.h
new file mode 100644
index 0000000000000..ccd623c0a3196
--- /dev/null
+++ b/libc/src/__support/math/atanh.h
@@ -0,0 +1,74 @@
+//===-- Implementation header for atanh -------------------------*- 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___SUPPORT_MATH_ATANH_H
+#define LLVM_LIBC_SRC___SUPPORT_MATH_ATANH_H
+
+#include "log1p.h"
+#include "src/__support/FPUtil/FEnvImpl.h"
+#include "src/__support/FPUtil/FPBits.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/macros/optimization.h" // LIBC_UNLIKELY
+
+namespace LIBC_NAMESPACE_DECL {
+
+namespace math {
+
+LIBC_INLINE double atanh(double x) {
+  using FPBits = fputil::FPBits<double>;
+  FPBits xbits(x);
+  Sign sign = xbits.sign();
+  uint64_t x_abs_u = xbits.abs().uintval();
+
+  // Handle NaN.
+  if (LIBC_UNLIKELY(xbits.is_nan())) {
+    if (xbits.is_signaling_nan()) {
+      fputil::raise_except_if_required(FE_INVALID);
+      return FPBits::quiet_nan().get_val();
+    }
+    return x;
+  }
+
+  // |x| >= 1.
+  if (LIBC_UNLIKELY(x_abs_u >= 0x3ff0000000000000ULL)) {
+    if (x_abs_u == 0x3ff0000000000000ULL) {
+      // |x| == 1: return +/-inf with ERANGE.
+      fputil::set_errno_if_required(ERANGE);
+      fputil::raise_except_if_required(FE_DIVBYZERO);
+      return FPBits::inf(sign).get_val();
+    }
+    // |x| > 1: domain error.
+    fputil::set_errno_if_required(EDOM);
+    fputil::raise_except_if_required(FE_INVALID);
+    return FPBits::quiet_nan().get_val();
+  }
+
+  // For very small |x| (|x| <= 2^-27), atanh(x) ~ x.
+  // Use FP arithmetic to ensure FTZ/DAZ mode behavior.
+  if (LIBC_UNLIKELY(x_abs_u <= 0x3e40000000000000ULL)) {
+    // atanh(+/-0) = +/-0, preserve sign of zero exactly.
+    if (LIBC_UNLIKELY(x_abs_u == 0))
+      return x;
+    double x_abs = xbits.abs().get_val();
+    double x2 = x_abs * x_abs;
+    double result = x_abs + x2 * x_abs * (1.0 / 3.0);
+    return sign == Sign::NEG ? -result : result;
+  }
+
+  // General case: atanh(x) = 0.5 * log((1+x)/(1-x))
+  //                        = 0.5 * log1p(2x / (1-x))
+  double x_abs = xbits.abs().get_val();
+  double result = 0.5 * math::log1p(2.0 * x_abs / (1.0 - x_abs));
+  return sign == Sign::NEG ? -result : result;
+}
+
+} // namespace math
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC___SUPPORT_MATH_ATANH_H
diff --git a/libc/src/__support/math/cosh.h b/libc/src/__support/math/cosh.h
new file mode 100644
index 0000000000000..a196a001ae0df
--- /dev/null
+++ b/libc/src/__support/math/cosh.h
@@ -0,0 +1,67 @@
+//===-- Implementation header for cosh --------------------------*- 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___SUPPORT_MATH_COSH_H
+#define LLVM_LIBC_SRC___SUPPORT_MATH_COSH_H
+
+#include "exp.h"
+#include "src/__support/FPUtil/FEnvImpl.h"
+#include "src/__support/FPUtil/FPBits.h"
+#include "src/__support/FPUtil/rounding_mode.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/macros/optimization.h" // LIBC_UNLIKELY
+
+namespace LIBC_NAMESPACE_DECL {
+
+namespace math {
+
+LIBC_INLINE double cosh(double x) {
+  using FPBits = fputil::FPBits<double>;
+  FPBits xbits(x);
+  // cosh is even, work with |x|.
+  xbits.set_sign(Sign::POS);
+  double x_abs = xbits.get_val();
+  uint64_t x_abs_u = xbits.uintval();
+
+  // Handle NaN: cosh(NaN) = NaN.
+  if (LIBC_UNLIKELY(xbits.is_nan())) {
+    if (xbits.is_signaling_nan()) {
+      fputil::raise_except_if_required(FE_INVALID);
+      return FPBits::quiet_nan().get_val();
+    }
+    return x;
+  }
+
+  // cosh(+/-inf) = +inf.
+  if (LIBC_UNLIKELY(xbits.is_inf()))
+    return FPBits::inf(Sign::POS).get_val();
+
+  // For very small |x| (|x| <= 2^-27), cosh(x) ~ 1.
+  if (LIBC_UNLIKELY(x_abs_u <= 0x3e40000000000000ULL))
+    return 1.0;
+
+  // For |x| >= 710, overflow.
+  if (LIBC_UNLIKELY(x_abs_u >= 0x4086340000000000ULL)) {
+    int rounding = fputil::quick_get_round();
+    if (rounding == FE_DOWNWARD || rounding == FE_TOWARDZERO)
+      return FPBits::max_normal().get_val();
+    fputil::set_errno_if_required(ERANGE);
+    fputil::raise_except_if_required(FE_OVERFLOW);
+    return FPBits::inf(Sign::POS).get_val();
+  }
+
+  // General case: cosh(x) = (exp(x) + exp(-x)) / 2.
+  double ex = math::exp(x_abs);
+  return (ex + 1.0 / ex) * 0.5;
+}
+
+} // namespace math
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC___SUPPORT_MATH_COSH_H
diff --git a/libc/src/__support/math/sinh.h b/libc/src/__support/math/sinh.h
new file mode 100644
index 0000000000000..daaf7e3f7c9ac
--- /dev/null
+++ b/libc/src/__support/math/sinh.h
@@ -0,0 +1,88 @@
+//===-- Implementation header for sinh --------------------------*- 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___SUPPORT_MATH_SINH_H
+#define LLVM_LIBC_SRC___SUPPORT_MATH_SINH_H
+
+#include "exp.h"
+#include "expm1.h"
+#include "src/__support/FPUtil/FEnvImpl.h"
+#include "src/__support/FPUtil/FPBits.h"
+#include "src/__support/FPUtil/rounding_mode.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/macros/optimization.h" // LIBC_UNLIKELY
+
+namespace LIBC_NAMESPACE_DECL {
+
+namespace math {
+
+LIBC_INLINE double sinh(double x) {
+  using FPBits = fputil::FPBits<double>;
+  FPBits xbits(x);
+  bool is_neg = xbits.is_neg();
+  // Work with |x|.
+  xbits.set_sign(Sign::POS);
+  double x_abs = xbits.get_val();
+  uint64_t x_abs_u = xbits.uintval();
+
+  // Handle NaN: sinh(NaN) = NaN.
+  if (LIBC_UNLIKELY(xbits.is_nan())) {
+    if (xbits.is_signaling_nan()) {
+      fputil::raise_except_if_required(FE_INVALID);
+      return FPBits::quiet_nan().get_val();
+    }
+    return x;
+  }
+
+  // sinh(+/-inf) = +/-inf.
+  if (LIBC_UNLIKELY(xbits.is_inf()))
+    return x;
+
+  // For very small |x| (|x| <= 2^-26), sinh(x) ~ x.
+  // Use FP arithmetic to ensure FTZ/DAZ mode behavior.
+  if (LIBC_UNLIKELY(x_abs_u <= 0x3e50000000000000ULL)) {
+    // sinh(+/-0) = +/-0, preserve sign of zero exactly.
+    if (LIBC_UNLIKELY(x_abs_u == 0))
+      return x;
+    double x2 = x_abs * x_abs;
+    double result = x_abs + x2 * x_abs * (1.0 / 6.0);
+    return is_neg ? -result : result;
+  }
+
+  // For |x| >= 710, overflow.
+  if (LIBC_UNLIKELY(x_abs_u >= 0x4086340000000000ULL)) {
+    int rounding = fputil::quick_get_round();
+    if ((rounding == FE_DOWNWARD && !is_neg) ||
+        (rounding == FE_UPWARD && is_neg) || rounding == FE_TOWARDZERO)
+      return is_neg ? -FPBits::max_normal().get_val()
+                    : FPBits::max_normal().get_val();
+    fputil::set_errno_if_required(ERANGE);
+    fputil::raise_except_if_required(FE_OVERFLOW);
+    return x + (is_neg ? -FPBits::inf().get_val() : FPBits::inf().get_val());
+  }
+
+  double result;
+  if (x_abs_u < 0x3ff0000000000000ULL) {
+    // |x| < 1: use expm1 to avoid catastrophic cancellation.
+    // sinh(x) = expm1(x)/2 + expm1(x)/(2*(1+expm1(x)))
+    double em1 = math::expm1(x_abs);
+    result = em1 / 2.0 + em1 / (2.0 * (1.0 + em1));
+  } else {
+    // |x| >= 1: sinh(x) = (exp(x) - exp(-x)) / 2.
+    double ex = math::exp(x_abs);
+    result = (ex - 1.0 / ex) * 0.5;
+  }
+
+  return is_neg ? -result : result;
+}
+
+} // namespace math
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC___SUPPORT_MATH_SINH_H
diff --git a/libc/src/__support/math/tanh.h b/libc/src/__support/math/tanh.h
new file mode 100644
index 0000000000000..052047a85dc0d
--- /dev/null
+++ b/libc/src/__support/math/tanh.h
@@ -0,0 +1,68 @@
+//===-- Implementation header for tanh --------------------------*- 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___SUPPORT_MATH_TANH_H
+#define LLVM_LIBC_SRC___SUPPORT_MATH_TANH_H
+
+#include "expm1.h"
+#include "src/__support/FPUtil/FEnvImpl.h"
+#include "src/__support/FPUtil/FPBits.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/macros/optimization.h" // LIBC_UNLIKELY
+
+namespace LIBC_NAMESPACE_DECL {
+
+namespace math {
+
+LIBC_INLINE double tanh(double x) {
+  using FPBits = fputil::FPBits<double>;
+  FPBits xbits(x);
+  bool is_neg = xbits.is_neg();
+  // Work with |x|.
+  xbits.set_sign(Sign::POS);
+  double x_abs = xbits.get_val();
+  uint64_t x_abs_u = xbits.uintval();
+
+  // Handle NaN: tanh(NaN) = NaN.
+  if (LIBC_UNLIKELY(xbits.is_nan())) {
+    if (xbits.is_signaling_nan()) {
+      fputil::raise_except_if_required(FE_INVALID);
+      return FPBits::quiet_nan().get_val();
+    }
+    return x;
+  }
+
+  // For very small |x| (|x| <= 2^-27), tanh(x) ~ x.
+  // Use x - x^3/3 to ensure FP operations are performed (for FTZ/DAZ modes).
+  if (LIBC_UNLIKELY(x_abs_u <= 0x3e40000000000000ULL)) {
+    // tanh(+/-0) = +/-0, preserve sign of zero exactly.
+    if (LIBC_UNLIKELY(x_abs_u == 0))
+      return x;
+    double x2 = x_abs * x_abs;
+    double result = x_abs - x2 * x_abs * (1.0 / 3.0);
+    return is_neg ? -result : result;
+  }
+
+  // For |x| >= 20, tanh(x) ~ sign(x) * 1.
+  // (e^40 - 1)/(e^40 + 1) rounds to 1 in double precision.
+  if (LIBC_UNLIKELY(x_abs_u >= 0x4034000000000000ULL)) {
+    // tanh(+/-inf) = +/-1.
+    return is_neg ? -1.0 : 1.0;
+  }
+
+  // General case: tanh(x) = expm1(2x) / (expm1(2x) + 2).
+  double e2xm1 = math::expm1(2.0 * x_abs);
+  double result = e2xm1 / (e2xm1 + 2.0);
+  return is_neg ? -result : result;
+}
+
+} // namespace math
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC___SUPPORT_MATH_TANH_H
diff --git a/libc/src/math/generic/CMakeLists.txt b/libc/src/math/generic/CMakeLists.txt
index 7ccbddba07b8d..50864369754b2 100644
--- a/libc/src/math/generic/CMakeLists.txt
+++ b/libc/src/math/generic/CMakeLists.txt
@@ -3631,6 +3631,16 @@ add_entrypoint_object(
     libc.src.__support.math.ufromfpxbf16
 )
 
+add_entrypoint_object(
+  cosh
+  SRCS
+    cosh.cpp
+  HDRS
+    ../cosh.h
+  DEPENDS
+    libc.src.__support.math.cosh
+)
+
 add_entrypoint_object(
   coshf
   SRCS
@@ -3651,6 +3661,16 @@ add_entrypoint_object(
     libc.src.__support.math.coshf16
 )
 
+add_entrypoint_object(
+  sinh
+  SRCS
+    sinh.cpp
+  HDRS
+    ../sinh.h
+  DEPENDS
+    libc.src.__support.math.sinh
+)
+
 add_entrypoint_object(
   sinhf
   SRCS
@@ -3...
[truncated]

@Sukumarsawant

Sukumarsawant commented May 25, 2026

Copy link
Copy Markdown
Contributor

Hi again! Do make separate PRs for each so its easier for reviewing

@iamaayushrivastava

Copy link
Copy Markdown
Contributor Author

Hi again! Do make separate PRs for each so its easier for reviewing

Ok, got it! I’ll split them into separate PRs to make reviewing easier.

@lntue

lntue commented May 25, 2026

Copy link
Copy Markdown
Contributor

Hi again! Do make separate PRs for each so its easier for reviewing

Ok, got it! I’ll split them into separate PRs to make reviewing easier.

Also add MPFR tests in libc/test/src/math folder, not just smoke tests, and they will need to be correctly rounded: https://libc.llvm.org/headers/math/index.html#implementation-requirements-goals

@Sukumarsawant

Copy link
Copy Markdown
Contributor

@iamaayushrivastava you can close this PR to avoid duplicate PRs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants