[llvm-branch-commits] [libc] [llvm] [libc][math] Implement double precision cosh correctly rounded to all rounding modes. (PR #235181)

via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Sun Oct 25 19:17:09 PDT 2026


https://github.com/lntue updated https://github.com/llvm/llvm-project/pull/235181

>From 44236bb03c3c5a42b782002a60d834da06ad5852 Mon Sep 17 00:00:00 2001
From: Tue Ly <lntue.h at gmail.com>
Date: Sat, 24 Oct 2026 02:17:10 +0000
Subject: [PATCH] [libc][math] Implement double precision cosh correctly
 rounded to all rounding modes.

Use shared exp(x) +- exp(-x) evaluations with sinh from https://github.com/llvm/llvm-project/pull/235097.

Assisted-by: Gemini for analysis and refactoring.
---
 libc/config/baremetal/aarch64/entrypoints.txt |   1 +
 libc/config/baremetal/arm/entrypoints.txt     |   1 +
 libc/config/baremetal/riscv/entrypoints.txt   |   1 +
 libc/config/darwin/aarch64/entrypoints.txt    |   1 +
 libc/config/freebsd/x86_64/entrypoints.txt    |   1 +
 libc/config/gpu/amdgpu/entrypoints.txt        |   1 +
 libc/config/gpu/nvptx/entrypoints.txt         |   1 +
 libc/config/linux/aarch64/entrypoints.txt     |   1 +
 libc/config/linux/arm/entrypoints.txt         |   1 +
 libc/config/linux/riscv/entrypoints.txt       |   1 +
 libc/config/linux/x86_64/entrypoints.txt      |   1 +
 libc/config/windows/entrypoints.txt           |   1 +
 libc/docs/headers/math/index.md               |   2 +-
 libc/include/math.yaml                        |   6 +
 libc/shared/math.h                            |   1 +
 libc/shared/math/cosh.h                       |  28 +++
 libc/src/__support/math/CMakeLists.txt        |  29 +++
 libc/src/__support/math/cosh.h                |  34 ++++
 libc/src/__support/math/cosh_double_eval.h    |  94 +++++++++
 libc/src/math/generic/CMakeLists.txt          |  10 +
 libc/src/math/generic/cosh.cpp                |  21 ++
 libc/test/shared/CMakeLists.txt               |   1 +
 libc/test/shared/shared_math_test.cpp         |   1 +
 libc/test/src/math/CMakeLists.txt             |  16 ++
 libc/test/src/math/cosh_test.cpp              | 187 ++++++++++++++++++
 libc/test/src/math/smoke/CMakeLists.txt       |  15 ++
 libc/test/src/math/smoke/cosh_test.cpp        | 125 ++++++++++++
 .../llvm-project-overlay/libc/BUILD.bazel     |  34 ++++
 .../libc/test/src/math/BUILD.bazel            |   8 +
 .../libc/test/src/math/smoke/BUILD.bazel      |   8 +
 30 files changed, 631 insertions(+), 1 deletion(-)
 create mode 100644 libc/shared/math/cosh.h
 create mode 100644 libc/src/__support/math/cosh.h
 create mode 100644 libc/src/__support/math/cosh_double_eval.h
 create mode 100644 libc/src/math/generic/cosh.cpp
 create mode 100644 libc/test/src/math/cosh_test.cpp
 create mode 100644 libc/test/src/math/smoke/cosh_test.cpp

diff --git a/libc/config/baremetal/aarch64/entrypoints.txt b/libc/config/baremetal/aarch64/entrypoints.txt
index db6033e9a2c04c..6ac24afcfbbcdc 100644
--- a/libc/config/baremetal/aarch64/entrypoints.txt
+++ b/libc/config/baremetal/aarch64/entrypoints.txt
@@ -385,6 +385,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
diff --git a/libc/config/baremetal/arm/entrypoints.txt b/libc/config/baremetal/arm/entrypoints.txt
index e4964aa02a6ec5..a71e3fc8cb1c8a 100644
--- a/libc/config/baremetal/arm/entrypoints.txt
+++ b/libc/config/baremetal/arm/entrypoints.txt
@@ -397,6 +397,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
diff --git a/libc/config/baremetal/riscv/entrypoints.txt b/libc/config/baremetal/riscv/entrypoints.txt
index d9baaaeba8a10e..1a481461473787 100644
--- a/libc/config/baremetal/riscv/entrypoints.txt
+++ b/libc/config/baremetal/riscv/entrypoints.txt
@@ -388,6 +388,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
diff --git a/libc/config/darwin/aarch64/entrypoints.txt b/libc/config/darwin/aarch64/entrypoints.txt
index cabd03e2442dfb..20ab7d6e180528 100644
--- a/libc/config/darwin/aarch64/entrypoints.txt
+++ b/libc/config/darwin/aarch64/entrypoints.txt
@@ -206,6 +206,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
diff --git a/libc/config/freebsd/x86_64/entrypoints.txt b/libc/config/freebsd/x86_64/entrypoints.txt
index 6d567b01571e84..4c256333d9228b 100644
--- a/libc/config/freebsd/x86_64/entrypoints.txt
+++ b/libc/config/freebsd/x86_64/entrypoints.txt
@@ -140,6 +140,7 @@ set(TARGET_LIBM_ENTRYPOINTS
     libc.src.math.ceill
     libc.src.math.cos
     libc.src.math.cosf
+    libc.src.math.cosh
     libc.src.math.coshf
     libc.src.math.daddl
     libc.src.math.ddivl
diff --git a/libc/config/gpu/amdgpu/entrypoints.txt b/libc/config/gpu/amdgpu/entrypoints.txt
index 196b6a3fd20266..880327cba1cbb0 100644
--- a/libc/config/gpu/amdgpu/entrypoints.txt
+++ b/libc/config/gpu/amdgpu/entrypoints.txt
@@ -331,6 +331,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.ddivl
diff --git a/libc/config/gpu/nvptx/entrypoints.txt b/libc/config/gpu/nvptx/entrypoints.txt
index 96305e3d1293f1..1c8a02af59a836 100644
--- a/libc/config/gpu/nvptx/entrypoints.txt
+++ b/libc/config/gpu/nvptx/entrypoints.txt
@@ -327,6 +327,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.ddivl
diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt
index f78eb4d3c8a372..79626a1ad727d7 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -627,6 +627,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.daddl
diff --git a/libc/config/linux/arm/entrypoints.txt b/libc/config/linux/arm/entrypoints.txt
index 3dd2eede1952d4..2d8bbf275517e9 100644
--- a/libc/config/linux/arm/entrypoints.txt
+++ b/libc/config/linux/arm/entrypoints.txt
@@ -423,6 +423,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.dfmal
     libc.src.math.dsqrtl
diff --git a/libc/config/linux/riscv/entrypoints.txt b/libc/config/linux/riscv/entrypoints.txt
index 689eeb8f6e3d69..a5f834f8e2c62d 100644
--- a/libc/config/linux/riscv/entrypoints.txt
+++ b/libc/config/linux/riscv/entrypoints.txt
@@ -684,6 +684,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.daddl
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index a42172e2cce6fa..110dca3a73ab72 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -706,6 +706,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.daddl
diff --git a/libc/config/windows/entrypoints.txt b/libc/config/windows/entrypoints.txt
index f38000fcddb7ac..4abd600c61d1dc 100644
--- a/libc/config/windows/entrypoints.txt
+++ b/libc/config/windows/entrypoints.txt
@@ -172,6 +172,7 @@ set(TARGET_LIBM_ENTRYPOINTS
     libc.src.math.ceill
     libc.src.math.cos
     libc.src.math.cosf
+    libc.src.math.cosh
     libc.src.math.coshf
     libc.src.math.daddl
     libc.src.math.ddivl
diff --git a/libc/docs/headers/math/index.md b/libc/docs/headers/math/index.md
index a4596408ee5648..e0393075cd900b 100644
--- a/libc/docs/headers/math/index.md
+++ b/libc/docs/headers/math/index.md
@@ -180,7 +180,7 @@ log
 | cbrt      | {{ check }}       | {{ check }}      |                         | {{ check }}           |                         | {{ check }}             | 7.12.7.1               | F.10.4.1                   |
 | compoundn |                   |                  |                         |                       |                         |                         | 7.12.7.2               | F.10.4.2                   |
 | cos       | {{ check }}       | {{ check }}      |                         | {{ check }}           |                         |                         | 7.12.4.5               | F.10.1.5                   |
-| cosh      | {{ check }}       |                  |                         | {{ check }}           |                         |                         | 7.12.5.4               | F.10.2.4                   |
+| cosh      | {{ check }}       | {{ check }}      |                         | {{ check }}           |                         |                         | 7.12.5.4               | F.10.2.4                   |
 | cospi     | {{ check }}       |                  |                         | {{ check }}           |                         |                         | 7.12.4.12              | F.10.1.12                  |
 | dsqrt     | N/A               | N/A              | {{ check }}             | N/A                   | {{ check }}\*           |                         | 7.12.14.6              | F.10.11                    |
 | erf       | {{ check }}       |                  |                         |                       |                         |                         | 7.12.8.1               | F.10.5.1                   |
diff --git a/libc/include/math.yaml b/libc/include/math.yaml
index 89e0ab4f0dac60..09668300f9ea88 100644
--- a/libc/include/math.yaml
+++ b/libc/include/math.yaml
@@ -323,6 +323,12 @@ functions:
     arguments:
       - type: _Float16
     guard: LIBC_TYPES_HAS_FLOAT16
+  - name: cosh
+    standards:
+      - stdc
+    return_type: double
+    arguments:
+      - type: double
   - name: coshf
     standards:
       - stdc
diff --git a/libc/shared/math.h b/libc/shared/math.h
index 04f85e000bd0b6..1f676b8da2e4f8 100644
--- a/libc/shared/math.h
+++ b/libc/shared/math.h
@@ -84,6 +84,7 @@
 #include "math/cos.h"
 #include "math/cosf.h"
 #include "math/cosf16.h"
+#include "math/cosh.h"
 #include "math/coshf.h"
 #include "math/coshf16.h"
 #include "math/cospif.h"
diff --git a/libc/shared/math/cosh.h b/libc/shared/math/cosh.h
new file mode 100644
index 00000000000000..7e21496ebcd9b4
--- /dev/null
+++ b/libc/shared/math/cosh.h
@@ -0,0 +1,28 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Shared cosh function.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SHARED_MATH_COSH_H
+#define LLVM_LIBC_SHARED_MATH_COSH_H
+
+#include "shared/libc_common.h"
+#include "src/__support/math/cosh.h"
+
+namespace LIBC_NAMESPACE_DECL {
+namespace shared {
+
+using math::cosh;
+
+} // namespace shared
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SHARED_MATH_COSH_H
diff --git a/libc/src/__support/math/CMakeLists.txt b/libc/src/__support/math/CMakeLists.txt
index 29b085e1d0c163..390220bd5a4b88 100644
--- a/libc/src/__support/math/CMakeLists.txt
+++ b/libc/src/__support/math/CMakeLists.txt
@@ -5772,6 +5772,35 @@ add_header_library(
     libc.src.__support.macros.properties.cpu_features
 )
 
+add_header_library(
+  cosh_double_eval
+  HDRS
+    cosh_double_eval.h
+  DEPENDS
+    .sinhcosh_utils
+    libc.hdr.errno_macros
+    libc.hdr.fenv_macros
+    libc.src.__support.FPUtil.except_value_utils
+    libc.src.__support.FPUtil.fenv_impl
+    libc.src.__support.FPUtil.fp_bits
+    libc.src.__support.FPUtil.rounding_mode
+    libc.src.__support.common
+    libc.src.__support.macros.attributes
+    libc.src.__support.macros.config
+    libc.src.__support.macros.optimization
+)
+
+add_header_library(
+  cosh
+  HDRS
+    cosh.h
+  DEPENDS
+    .cosh_double_eval
+    libc.src.__support.common
+    libc.src.__support.macros.config
+    libc.src.__support.macros.optimization
+)
+
 add_header_library(
   sinh_double_eval
   HDRS
diff --git a/libc/src/__support/math/cosh.h b/libc/src/__support/math/cosh.h
new file mode 100644
index 00000000000000..07248d0b0023b3
--- /dev/null
+++ b/libc/src/__support/math/cosh.h
@@ -0,0 +1,34 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Implementation header for cosh.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC___SUPPORT_MATH_COSH_H
+#define LLVM_LIBC_SRC___SUPPORT_MATH_COSH_H
+
+#include "src/__support/common.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/macros/optimization.h"
+
+#include "src/__support/math/cosh_double_eval.h"
+#define LIBC_MATH_COSH_IMPL double_eval
+
+namespace LIBC_NAMESPACE_DECL {
+namespace math {
+
+using LIBC_MATH_COSH_IMPL::cosh;
+
+} // namespace math
+} // namespace LIBC_NAMESPACE_DECL
+
+#undef LIBC_MATH_COSH_IMPL
+
+#endif // LLVM_LIBC_SRC___SUPPORT_MATH_COSH_H
diff --git a/libc/src/__support/math/cosh_double_eval.h b/libc/src/__support/math/cosh_double_eval.h
new file mode 100644
index 00000000000000..0511cee00cdaa3
--- /dev/null
+++ b/libc/src/__support/math/cosh_double_eval.h
@@ -0,0 +1,94 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Double-precision implementation of cosh.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC___SUPPORT_MATH_COSH_DOUBLE_EVAL_H
+#define LLVM_LIBC_SRC___SUPPORT_MATH_COSH_DOUBLE_EVAL_H
+
+#include "hdr/errno_macros.h"
+#include "hdr/fenv_macros.h"
+#include "src/__support/FPUtil/FEnvImpl.h"
+#include "src/__support/FPUtil/FPBits.h"
+#include "src/__support/FPUtil/except_value_utils.h"
+#include "src/__support/FPUtil/rounding_mode.h"
+#include "src/__support/common.h"
+#include "src/__support/macros/attributes.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/macros/optimization.h"
+#include "src/__support/math/sinhcosh_utils.h"
+
+namespace LIBC_NAMESPACE_DECL {
+namespace math {
+namespace double_eval {
+
+// Handle tiny (|x| <= 2^-27), overflow (|x| >= 0x1.633ce8fb9f87ep+9), inf, and
+// nan for cosh(x):
+LIBC_NOINLINE LIBC_INLINE double cosh_exceptional(double x) {
+  using FPBits = fputil::FPBits<double>;
+  FPBits xbits(x);
+
+  // Tiny and subnormal range: |x| <= 2^-27.
+  // For 0 < |x| <= 2^-27:
+  //   0 < cosh(x) - 1 ~ x^2 / 2 < 2^-55 < 0.5 * ulp(1.0),
+  // so cosh(x) rounds to 1.0 + 2^-52 when rounding mode is FE_UPWARD and to
+  // 1.0 otherwise, with FE_INEXACT raised for all x != 0.
+  if (xbits.abs().uintval() <= 0x3e40'0000'0000'0000ULL) {
+    if (LIBC_UNLIKELY(x == 0.0))
+      return 1.0;
+    return fputil::round_result_slightly_up(1.0);
+  }
+
+  if (xbits.is_nan())
+    return x + 1.0;
+
+  if (xbits.is_inf())
+    return FPBits::inf().get_val();
+
+  fputil::set_errno_if_required(ERANGE);
+  fputil::raise_overflow_except_if_required<double>();
+
+#ifndef LIBC_MATH_HAS_ASSUME_ROUND_NEAREST_ONLY
+  int rounding = fputil::quick_get_round();
+  if (LIBC_UNLIKELY(rounding == FE_DOWNWARD || rounding == FE_TOWARDZERO))
+    return FPBits::max_normal().get_val();
+#endif // !LIBC_MATH_HAS_ASSUME_ROUND_NEAREST_ONLY
+
+  return FPBits::inf().get_val();
+}
+
+LIBC_ALWAYS_INLINE double cosh(double x) {
+  using FPBits = fputil::FPBits<double>;
+  FPBits xbits(x);
+
+  uint64_t x_abs = xbits.abs().uintval();
+
+  // Bound constants (with biased exponent and mantissa bits):
+  // - Tiny bound: 2^-27 = 0x1.0p-27
+  constexpr uint64_t TINY_BOUND = 0x3e40'0000'0000'0000ULL;
+  // - Overflow bound:
+  //   > round(acosh(2^1023 * (2 - 2^-52)), D, RU) = 0x1.633ce8fb9f87ep+9
+  //   > round(acosh(2^1023 * (2 - 2^-52)), D, RD) = 0x1.633ce8fb9f87dp+9
+  //   > round(cosh(0x1.633ce8fb9f87dp+9), D, RN)  = 0x1.ffffffffffd3bp+1023
+  //   > round(cosh(0x1.633ce8fb9f87ep+9), D, RN)  = +inf
+  constexpr uint64_t OVERFLOW_BOUND = 0x4086'33ce'8fb9'f87eULL;
+
+  if (LIBC_UNLIKELY(x_abs <= TINY_BOUND || x_abs >= OVERFLOW_BOUND))
+    return cosh_exceptional(x);
+
+  return exp_pm_eval(xbits.abs().get_val(), /*is_sinh=*/false);
+}
+
+} // namespace double_eval
+} // namespace math
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC___SUPPORT_MATH_COSH_DOUBLE_EVAL_H
diff --git a/libc/src/math/generic/CMakeLists.txt b/libc/src/math/generic/CMakeLists.txt
index 6b025c4fa60620..9f0325832248ec 100644
--- a/libc/src/math/generic/CMakeLists.txt
+++ b/libc/src/math/generic/CMakeLists.txt
@@ -3733,6 +3733,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
diff --git a/libc/src/math/generic/cosh.cpp b/libc/src/math/generic/cosh.cpp
new file mode 100644
index 00000000000000..3d34c90f1a54ce
--- /dev/null
+++ b/libc/src/math/generic/cosh.cpp
@@ -0,0 +1,21 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Double-precision cosh(x) function.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/math/cosh.h"
+#include "src/__support/math/cosh.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(double, cosh, (double x)) { return math::cosh(x); }
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/test/shared/CMakeLists.txt b/libc/test/shared/CMakeLists.txt
index 80c3eb1e1a8f20..f902fa553e49ef 100644
--- a/libc/test/shared/CMakeLists.txt
+++ b/libc/test/shared/CMakeLists.txt
@@ -93,6 +93,7 @@ else()
       libc.src.__support.math.cos
       libc.src.__support.math.cosf
       libc.src.__support.math.cosf16
+      libc.src.__support.math.cosh
       libc.src.__support.math.coshf
       libc.src.__support.math.coshf16
       libc.src.__support.math.cospif
diff --git a/libc/test/shared/shared_math_test.cpp b/libc/test/shared/shared_math_test.cpp
index 1d70b4d3578203..73a5cb45ed2b9c 100644
--- a/libc/test/shared/shared_math_test.cpp
+++ b/libc/test/shared/shared_math_test.cpp
@@ -337,6 +337,7 @@ TEST(LlvmLibcSharedMathTest, AllDouble) {
   EXPECT_FP_EQ(0.0, LIBC_NAMESPACE::shared::atan2(0.0, 0.0));
   EXPECT_FP_EQ(0.0, LIBC_NAMESPACE::shared::cbrt(0.0));
   EXPECT_FP_EQ(1.0, LIBC_NAMESPACE::shared::cos(0.0));
+  EXPECT_FP_EQ(1.0, LIBC_NAMESPACE::shared::cosh(0.0));
   EXPECT_FP_EQ(1.0, LIBC_NAMESPACE::shared::exp(0.0));
   EXPECT_FP_EQ(1.0, LIBC_NAMESPACE::shared::exp2(0.0));
   EXPECT_FP_EQ(1.0, LIBC_NAMESPACE::shared::exp10(0.0));
diff --git a/libc/test/src/math/CMakeLists.txt b/libc/test/src/math/CMakeLists.txt
index 394f347782807b..9ec8422b57f081 100644
--- a/libc/test/src/math/CMakeLists.txt
+++ b/libc/test/src/math/CMakeLists.txt
@@ -2568,6 +2568,22 @@ add_fp_unittest(
     libc.src.__support.FPUtil.fp_bits
 )
 
+add_fp_unittest(
+  cosh_test
+  NEED_MPFR
+  SUITE
+    libc-math-tests
+  SRCS
+    cosh_test.cpp
+  DEPENDS
+    libc.hdr.fenv_macros
+    libc.src.math.cosh
+    libc.src.__support.math.cosh_double_eval
+    libc.src.__support.FPUtil.fp_bits
+    libc.src.__support.macros.attributes
+    libc.src.__support.macros.optimization
+)
+
 add_fp_unittest(
   coshf_test
   NEED_MPFR
diff --git a/libc/test/src/math/cosh_test.cpp b/libc/test/src/math/cosh_test.cpp
new file mode 100644
index 00000000000000..150528bb0e572d
--- /dev/null
+++ b/libc/test/src/math/cosh_test.cpp
@@ -0,0 +1,187 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// This file contains unittests for cosh.
+///
+//===----------------------------------------------------------------------===//
+
+#include "hdr/fenv_macros.h"
+#include "hdr/math_macros.h"
+#include "hdr/stdint_proxy.h"
+#include "src/__support/FPUtil/FPBits.h"
+#include "src/__support/macros/attributes.h"
+#include "src/__support/macros/optimization.h"
+#include "src/__support/math/cosh_double_eval.h"
+#include "src/math/cosh.h"
+#include "test/UnitTest/FPMatcher.h"
+#include "test/UnitTest/Test.h"
+#include "utils/MPFRWrapper/MPFRUtils.h"
+
+#ifdef LIBC_MATH_HAS_SKIP_ACCURATE_PASS
+#define TOLERANCE 1
+#else // !LIBC_MATH_HAS_SKIP_ACCURATE_PASS
+#define TOLERANCE 0
+#endif // LIBC_MATH_HAS_SKIP_ACCURATE_PASS
+
+#ifdef LIBC_MATH_HAS_ASSUME_ROUND_NEAREST_ONLY
+#define ALL_ROUNDING false
+#else
+#define ALL_ROUNDING true
+#endif // LIBC_MATH_HAS_ASSUME_ROUND_NEAREST_ONLY
+
+namespace mpfr = LIBC_NAMESPACE::testing::mpfr;
+using LIBC_NAMESPACE::testing::tlog;
+
+LIBC_NOINLINE double cosh_double_eval(double x) {
+  return LIBC_NAMESPACE::math::double_eval::cosh(x);
+}
+
+class CoshTest : public LIBC_NAMESPACE::testing::FPTest<double> {
+public:
+  void test_special_numbers(double (*func)(double)) {
+    EXPECT_FP_EQ(aNaN, func(aNaN));
+    EXPECT_FP_EQ(inf, func(inf));
+    EXPECT_FP_EQ_ALL_ROUNDING(inf, func(neg_inf));
+    EXPECT_FP_EQ_ALL_ROUNDING(1.0, func(0.0));
+    EXPECT_FP_EQ_ALL_ROUNDING(1.0, func(-0.0));
+  }
+
+  void test_overflow(double (*func)(double)) {
+    EXPECT_FP_EQ_WITH_EXCEPTION(inf, func(0x1.0p20), FE_OVERFLOW);
+    EXPECT_FP_EQ_WITH_EXCEPTION(inf, func(-0x1.0p20), FE_OVERFLOW);
+  }
+
+  void test_tricky_inputs(double (*func)(double), double ulp_tolerance = 0.5,
+                          bool all_rounding = true) {
+    constexpr int N = 20;
+    constexpr uint64_t INPUTS[N] = {
+        0x3e40'0000'0000'0000ULL, // x = 0x1.0p-27
+        0x3e40'0000'0000'0001ULL, // x = 0x1.0000000000001p-27
+        0x3eb0'1fe0'3f61'baceULL, // x = 0x1.01fe03f61bacep-20
+        0x3f10'1218'4c8c'9313ULL, // x = 0x1.012184c8c9313p-14
+        0x3f80'1813'9e73'ee47ULL, // x = 0x1.018139e73ee47p-7
+        0x3fa0'10b2'a872'e30bULL, // x = 0x1.010b2a872e30bp-5
+        0x3fb9'a5e3'cbe1'985eULL, // x = 0x1.9a5e3cbe1985ep-4
+        0x3fc5'2a11'832e'847dULL, // x = 0x1.52a11832e847dp-3
+        0x3fcb'f030'5e2c'6c37ULL, // x = 0x1.bf0305e2c6c37p-3
+        0x3fd1'7326'ffc0'9f68ULL, // x = 0x1.17326ffc09f68p-2
+        0x3fd3'd27b'f16d'8bdbULL, // x = 0x1.3d27bf16d8bdbp-2
+        0x3fe0'3923'f2b4'7c07ULL, // x = 0x1.03923f2b47c07p-1
+        0x3fea'6031'cd5f'93baULL, // x = 0x1.a6031cd5f93bap-1
+        0x3ff1'04b6'48f1'13a1ULL, // x = 0x1.104b648f113a1p+0
+        0x3ff1'5857'20f3'5cd9ULL, // x = 0x1.1585720f35cd9p+0
+        0x3ffe'9cc7'ed2e'1a7eULL, // x = 0x1.e9cc7ed2e1a7ep+0
+        0x4004'3180'ea85'4696ULL, // x = 0x1.43180ea854696p+1
+        0x4017'2581'1dcf'6782ULL, // x = 0x1.725811dcf6782p+2
+        0x4086'33ce'8fb9'f87dULL, // x = 0x1.633ce8fb9f87dp+9
+        0x4086'33ce'8fb9'f87eULL, // x = 0x1.633ce8fb9f87ep+9
+    };
+    for (int i = 0; i < N; ++i) {
+      double x = FPBits(INPUTS[i]).get_val();
+      if (all_rounding) {
+        EXPECT_MPFR_MATCH_ALL_ROUNDING(mpfr::Operation::Cosh, x, func(x),
+                                       ulp_tolerance);
+        EXPECT_MPFR_MATCH_ALL_ROUNDING(mpfr::Operation::Cosh, -x, func(-x),
+                                       ulp_tolerance);
+      } else {
+        EXPECT_MPFR_MATCH(mpfr::Operation::Cosh, x, func(x), ulp_tolerance);
+        EXPECT_MPFR_MATCH(mpfr::Operation::Cosh, -x, func(-x), ulp_tolerance);
+      }
+    }
+  }
+
+  void test_in_range(double (*func)(double), double ulp_tolerance = 0.5,
+                     bool all_rounding = true) {
+    constexpr uint64_t COUNT = 1'231;
+    constexpr uint64_t START = FPBits(0x1.0p-30).uintval();
+    constexpr uint64_t STOP = FPBits(710.0).uintval();
+    constexpr uint64_t STEP = (STOP - START) / COUNT;
+
+    auto test = [&](mpfr::RoundingMode rounding_mode) {
+      mpfr::ForceRoundingMode __r(rounding_mode);
+      if (!__r.success)
+        return;
+
+      uint64_t fails = 0;
+      uint64_t count = 0;
+      uint64_t cc = 0;
+      double mx = 0.0, mr = 0.0;
+      double tol = 0.5;
+
+      for (uint64_t i = 0, v = START; i <= COUNT; ++i, v += STEP) {
+        double x = FPBits(v).get_val();
+        if (FPBits(v).is_nan() || FPBits(v).is_inf() || x < 0.0)
+          continue;
+        double result = func(x);
+        ++cc;
+        if (FPBits(result).is_nan() || FPBits(result).is_inf())
+          continue;
+
+        ++count;
+        if (!TEST_MPFR_MATCH_ROUNDING_SILENTLY(mpfr::Operation::Cosh, x, result,
+                                               ulp_tolerance, rounding_mode)) {
+          ++fails;
+          while (!TEST_MPFR_MATCH_ROUNDING_SILENTLY(
+              mpfr::Operation::Cosh, x, result, tol, rounding_mode)) {
+            mx = x;
+            mr = result;
+
+            if (tol > 1000.0)
+              break;
+
+            tol *= 2.0;
+          }
+        }
+      }
+      if (fails) {
+        tlog << " Cosh failed: " << fails << "/" << count << "/" << cc
+             << " tests.\n";
+        tlog << "   Max ULPs is at most: " << static_cast<uint64_t>(tol)
+             << ".\n";
+        EXPECT_MPFR_MATCH(mpfr::Operation::Cosh, mx, mr, ulp_tolerance,
+                          rounding_mode);
+      }
+    };
+
+    tlog << " Test Rounding To Nearest...\n";
+    test(mpfr::RoundingMode::Nearest);
+
+    if (all_rounding) {
+      tlog << " Test Rounding Downward...\n";
+      test(mpfr::RoundingMode::Downward);
+
+      tlog << " Test Rounding Upward...\n";
+      test(mpfr::RoundingMode::Upward);
+
+      tlog << " Test Rounding Toward Zero...\n";
+      test(mpfr::RoundingMode::TowardZero);
+    }
+  }
+};
+
+#define LIST_COSH_TESTS(suffix, func, ulp_tolerance, all_rounding)             \
+  using LlvmLibcCoshTest##suffix = CoshTest;                                   \
+  TEST_F(LlvmLibcCoshTest##suffix, SpecialNumbers) {                           \
+    test_special_numbers(&func);                                               \
+  }                                                                            \
+  TEST_F(LlvmLibcCoshTest##suffix, Overflow) { test_overflow(&func); }         \
+  TEST_F(LlvmLibcCoshTest##suffix, TrickyInputs) {                             \
+    test_tricky_inputs(&func, ulp_tolerance, all_rounding);                    \
+  }                                                                            \
+  TEST_F(LlvmLibcCoshTest##suffix, InDoubleRange) {                            \
+    test_in_range(&func, ulp_tolerance, all_rounding);                         \
+  }                                                                            \
+  static_assert(true, "Require semicolon.")
+
+LIST_COSH_TESTS(Default, LIBC_NAMESPACE::cosh,
+                /*ulp_tolerance=*/TOLERANCE + 0.5,
+                /*all_rounding=*/ALL_ROUNDING);
+LIST_COSH_TESTS(DoubleEval, cosh_double_eval,
+                /*ulp_tolerance=*/TOLERANCE + 0.5,
+                /*all_rounding=*/ALL_ROUNDING);
diff --git a/libc/test/src/math/smoke/CMakeLists.txt b/libc/test/src/math/smoke/CMakeLists.txt
index 448add6cf8a73f..7485f5409402f6 100644
--- a/libc/test/src/math/smoke/CMakeLists.txt
+++ b/libc/test/src/math/smoke/CMakeLists.txt
@@ -4732,6 +4732,21 @@ add_fp_unittest(
     libc.src.__support.FPUtil.fenv_impl
 )
 
+add_fp_unittest(
+  cosh_test
+  SUITE
+    libc-math-smoke-tests
+  SRCS
+    cosh_test.cpp
+  DEPENDS
+    libc.hdr.errno_macros
+    libc.hdr.fenv_macros
+    libc.src.math.cosh
+    libc.src.__support.math.cosh_double_eval
+    libc.src.__support.FPUtil.fp_bits
+    libc.src.__support.macros.attributes
+)
+
 add_fp_unittest(
   coshf_test
   SUITE
diff --git a/libc/test/src/math/smoke/cosh_test.cpp b/libc/test/src/math/smoke/cosh_test.cpp
new file mode 100644
index 00000000000000..757a6d2feb3c16
--- /dev/null
+++ b/libc/test/src/math/smoke/cosh_test.cpp
@@ -0,0 +1,125 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// This file contains smoke tests for cosh.
+///
+//===----------------------------------------------------------------------===//
+
+#include "hdr/errno_macros.h"
+#include "hdr/fenv_macros.h"
+#include "hdr/math_macros.h"
+#include "hdr/stdint_proxy.h"
+#include "src/__support/FPUtil/FPBits.h"
+#include "src/__support/macros/attributes.h"
+#include "src/__support/math/cosh_double_eval.h"
+#include "src/math/cosh.h"
+#include "test/UnitTest/FPMatcher.h"
+#include "test/UnitTest/Test.h"
+
+LIBC_NOINLINE double cosh_double_eval(double x) {
+  return LIBC_NAMESPACE::math::double_eval::cosh(x);
+}
+
+class CoshTest : public LIBC_NAMESPACE::testing::FPTest<double> {
+public:
+  void test_special_numbers(double (*func)(double),
+                            bool check_snan_invalid = false) {
+    if (check_snan_invalid) {
+      EXPECT_FP_EQ_WITH_EXCEPTION(aNaN, func(sNaN), FE_INVALID);
+    } else {
+      EXPECT_FP_EQ(aNaN, func(sNaN));
+    }
+    EXPECT_MATH_ERRNO(0);
+
+    EXPECT_FP_EQ_ALL_ROUNDING(aNaN, func(aNaN));
+    EXPECT_MATH_ERRNO(0);
+
+    EXPECT_FP_EQ_ALL_ROUNDING(inf, func(inf));
+    EXPECT_MATH_ERRNO(0);
+
+    EXPECT_FP_EQ_ALL_ROUNDING(inf, func(neg_inf));
+    EXPECT_MATH_ERRNO(0);
+
+    EXPECT_FP_EQ_ALL_ROUNDING(1.0, func(0.0));
+    EXPECT_MATH_ERRNO(0);
+
+    EXPECT_FP_EQ_ALL_ROUNDING(1.0, func(-0.0));
+    EXPECT_MATH_ERRNO(0);
+  }
+
+  void test_overflow(double (*func)(double)) {
+    // x = 0x1.fffffffffffffp+1023 (max_normal)
+    EXPECT_FP_EQ_WITH_EXCEPTION(
+        inf, func(FPBits(uint64_t(0x7fef'ffff'ffff'ffff)).get_val()),
+        FE_OVERFLOW);
+    EXPECT_MATH_ERRNO(ERANGE);
+
+    // x = 0x1.633ce8fb9f87ep+9 (smallest positive input that overflows)
+    EXPECT_FP_EQ_WITH_EXCEPTION(
+        inf, func(FPBits(uint64_t(0x4086'33ce'8fb9'f87e)).get_val()),
+        FE_OVERFLOW);
+    EXPECT_MATH_ERRNO(ERANGE);
+
+    // x = -0x1.633ce8fb9f87ep+9 (largest negative input that overflows)
+    EXPECT_FP_EQ_WITH_EXCEPTION(
+        inf, func(FPBits(uint64_t(0xc086'33ce'8fb9'f87e)).get_val()),
+        FE_OVERFLOW);
+    EXPECT_MATH_ERRNO(ERANGE);
+  }
+
+  void test_tiny(double (*func)(double)) {
+    EXPECT_FP_EQ(1.0, func(min_denormal));
+    EXPECT_MATH_ERRNO(0);
+
+    EXPECT_FP_EQ(1.0, func(-min_denormal));
+    EXPECT_MATH_ERRNO(0);
+
+    EXPECT_FP_EQ(1.0, func(0x1.0p-27));
+    EXPECT_MATH_ERRNO(0);
+  }
+
+#ifdef LIBC_TEST_FTZ_DAZ
+  void test_denormals(double (*func)(double)) {
+    EXPECT_FP_EQ(1.0, func(min_denormal));
+    EXPECT_FP_EQ(1.0, func(max_denormal));
+  }
+#endif // LIBC_TEST_FTZ_DAZ
+};
+
+#ifdef LIBC_TEST_FTZ_DAZ
+#define LIST_COSH_FTZ_DAZ_TESTS(suffix, func)                                  \
+  TEST_F(LlvmLibcCoshTest##suffix, FTZMode) {                                  \
+    LIBC_NAMESPACE::testing::ModifyMXCSR mxcsr(LIBC_NAMESPACE::testing::FTZ);  \
+    test_denormals(&func);                                                     \
+  }                                                                            \
+  TEST_F(LlvmLibcCoshTest##suffix, DAZMode) {                                  \
+    LIBC_NAMESPACE::testing::ModifyMXCSR mxcsr(LIBC_NAMESPACE::testing::DAZ);  \
+    test_denormals(&func);                                                     \
+  }                                                                            \
+  TEST_F(LlvmLibcCoshTest##suffix, FTZDAZMode) {                               \
+    LIBC_NAMESPACE::testing::ModifyMXCSR mxcsr(LIBC_NAMESPACE::testing::FTZ |  \
+                                               LIBC_NAMESPACE::testing::DAZ);  \
+    test_denormals(&func);                                                     \
+  }
+#else // !LIBC_TEST_FTZ_DAZ
+#define LIST_COSH_FTZ_DAZ_TESTS(suffix, func)
+#endif // LIBC_TEST_FTZ_DAZ
+
+#define LIST_COSH_TESTS(suffix, func, check_snan_invalid)                      \
+  using LlvmLibcCoshTest##suffix = CoshTest;                                   \
+  TEST_F(LlvmLibcCoshTest##suffix, SpecialNumbers) {                           \
+    test_special_numbers(&func, check_snan_invalid);                           \
+  }                                                                            \
+  TEST_F(LlvmLibcCoshTest##suffix, Overflow) { test_overflow(&func); }         \
+  TEST_F(LlvmLibcCoshTest##suffix, Tiny) { test_tiny(&func); }                 \
+  LIST_COSH_FTZ_DAZ_TESTS(suffix, func)                                        \
+  static_assert(true, "Require semicolon.")
+
+LIST_COSH_TESTS(Default, LIBC_NAMESPACE::cosh, /*check_snan_invalid=*/true);
+LIST_COSH_TESTS(DoubleEval, cosh_double_eval, /*check_snan_invalid=*/false);
diff --git a/utils/bazel/llvm-project-overlay/libc/BUILD.bazel b/utils/bazel/llvm-project-overlay/libc/BUILD.bazel
index 299993bbebba70..83cd98d6422cbc 100644
--- a/utils/bazel/llvm-project-overlay/libc/BUILD.bazel
+++ b/utils/bazel/llvm-project-overlay/libc/BUILD.bazel
@@ -8702,6 +8702,35 @@ libc_support_library(
     ],
 )
 
+libc_support_library(
+    name = "__support_math_cosh_double_eval",
+    hdrs = ["src/__support/math/cosh_double_eval.h"],
+    deps = [
+        ":__support_common",
+        ":__support_fputil_except_value_utils",
+        ":__support_fputil_fenv_impl",
+        ":__support_fputil_fp_bits",
+        ":__support_fputil_rounding_mode",
+        ":__support_macros_attributes",
+        ":__support_macros_config",
+        ":__support_macros_optimization",
+        ":__support_math_sinhcosh_utils",
+        ":hdr_errno_macros",
+        ":hdr_fenv_macros",
+    ],
+)
+
+libc_support_library(
+    name = "__support_math_cosh",
+    hdrs = ["src/__support/math/cosh.h"],
+    deps = [
+        ":__support_common",
+        ":__support_macros_config",
+        ":__support_macros_optimization",
+        ":__support_math_cosh_double_eval",
+    ],
+)
+
 libc_support_library(
     name = "__support_math_coshf",
     hdrs = ["src/__support/math/coshf.h"],
@@ -13040,6 +13069,11 @@ libc_math_function(
     ],
 )
 
+libc_math_function(
+    name = "cosh",
+    additional_deps = [":__support_math_cosh"],
+)
+
 libc_math_function(
     name = "coshf",
     additional_deps = [":__support_math_coshf"],
diff --git a/utils/bazel/llvm-project-overlay/libc/test/src/math/BUILD.bazel b/utils/bazel/llvm-project-overlay/libc/test/src/math/BUILD.bazel
index 89289849d4ba73..46cf02bf9e7271 100644
--- a/utils/bazel/llvm-project-overlay/libc/test/src/math/BUILD.bazel
+++ b/utils/bazel/llvm-project-overlay/libc/test/src/math/BUILD.bazel
@@ -129,6 +129,14 @@ math_mpfr_test(
     ],
 )
 
+math_mpfr_test(
+    name = "cosh",
+    deps = [
+        "//libc:__support_macros_attributes",
+        "//libc:__support_math_cosh_double_eval",
+    ],
+)
+
 math_mpfr_test(
     name = "coshf",
     deps = [
diff --git a/utils/bazel/llvm-project-overlay/libc/test/src/math/smoke/BUILD.bazel b/utils/bazel/llvm-project-overlay/libc/test/src/math/smoke/BUILD.bazel
index e5bd15412d0611..b47989fcb6b5fb 100644
--- a/utils/bazel/llvm-project-overlay/libc/test/src/math/smoke/BUILD.bazel
+++ b/utils/bazel/llvm-project-overlay/libc/test/src/math/smoke/BUILD.bazel
@@ -250,6 +250,14 @@ math_test(name = "cos")
 
 math_test(name = "cosf")
 
+math_test(
+    name = "cosh",
+    deps = [
+        "//libc:__support_macros_attributes",
+        "//libc:__support_math_cosh_double_eval",
+    ],
+)
+
 math_test(
     name = "coshf",
     deps = [



More information about the llvm-branch-commits mailing list