[libc-commits] [libc] [libc][math] Correct tanhf16 subnormal rounding and range reporting (PR #236480)

via libc-commits libc-commits at lists.llvm.org
Fri Oct 30 07:56:11 PDT 2026


https://github.com/sriramshastry updated https://github.com/llvm/llvm-project/pull/236480

>From dd35c847f99876802736ff8ba328cbf14348ce67 Mon Sep 17 00:00:00 2001
From: sriramshastry <sriramshastry at gmail.com>
Date: Wed, 28 Oct 2026 16:21:34 +0000
Subject: [PATCH] [libc][math] Correct tanhf16 subnormal rounding and range
 reporting

Select the rounded neighbor from the nonzero correction toward zero
for half subnormals and adjacent signed minimum-normal inputs.
Preserve signed zero and report inexact, with underflow and ERANGE
when the rounded result is tiny.

Add exhaustive signed-subnormal and minimum-normal all-mode regressions.

Signed-off-by: sriramshastry <sriramshastry at gmail.com>
---
 libc/src/__support/math/tanhf16.h         | 52 +++++++++++------
 libc/test/src/math/smoke/tanhf16_test.cpp | 68 +++++++++++++++++++++++
 2 files changed, 103 insertions(+), 17 deletions(-)

diff --git a/libc/src/__support/math/tanhf16.h b/libc/src/__support/math/tanhf16.h
index 768ec6845fe946..e776ac73fc20c0 100644
--- a/libc/src/__support/math/tanhf16.h
+++ b/libc/src/__support/math/tanhf16.h
@@ -49,8 +49,7 @@ LIBC_INLINE float16 tanhf16(float16 x) {
   uint16_t x_u = x_bits.uintval();
   uint16_t x_abs = x_u & 0x7fffU;
 
-  // When -2^(-14) <= x <= -2^(-9), or |x| <= 0x1.d2p-4,
-  // or |x| >= atanh(1 - 2^(-11)), or x is NaN.
+  // When |x| <= 0x1.d2p-4, |x| >= atanh(1 - 2^(-11)), or x is NaN.
   if (LIBC_UNLIKELY(x_abs <= 0x2f48U || x_abs >= 0x4429U)) {
     // tanh(NaN) = NaN
     if (x_bits.is_nan()) {
@@ -62,26 +61,45 @@ LIBC_INLINE float16 tanhf16(float16 x) {
       return x;
     }
 
-    // When -2^(-14) <= x <= -2^(-9).
-    if (x_u >= 0x8400U && x_u <= 0x9800U) {
-#ifdef LIBC_MATH_HAS_ASSUME_ROUND_NEAREST_ONLY
-      return x;
-#else
-      switch (fputil::quick_get_round()) {
-      case FE_TONEAREST:
-      case FE_DOWNWARD:
-        return x;
-      default:
-        return FPBits(static_cast<uint16_t>(x_u - 1U)).get_val();
-      }
-#endif // LIBC_MATH_HAS_ASSUME_ROUND_NEAREST_ONLY
-    }
-
     // When |x| <= 0x1.d2p-4.
     if (x_abs <= 0x2f48U) {
       if (LIBC_UNLIKELY(x_abs == 0))
         return x;
 
+      // The correction to x is nonzero but less than half a subnormal ulp.
+      // Select the adjacent half value directly, preserving signed zero.
+      if (LIBC_UNLIKELY(x_abs <= 0x0400U)) {
+        uint16_t out = x_u;
+#ifndef LIBC_MATH_HAS_ASSUME_ROUND_NEAREST_ONLY
+        int mode = fputil::quick_get_round();
+        if (mode == FE_TOWARDZERO || (x_bits.is_pos() && mode == FE_DOWNWARD) ||
+            (x_bits.is_neg() && mode == FE_UPWARD))
+          --out;
+#endif
+        int excepts = FE_INEXACT;
+        if ((out & 0x7fffU) < 0x0400U) {
+          fputil::set_errno_if_required(ERANGE);
+          excepts |= FE_UNDERFLOW;
+        }
+        fputil::raise_except_if_required(excepts);
+        return FPBits(out).get_val();
+      }
+
+      // When -2^(-9) <= x < -2^(-14).
+      if (x_u >= 0x8400U && x_u <= 0x9800U) {
+#ifdef LIBC_MATH_HAS_ASSUME_ROUND_NEAREST_ONLY
+        return x;
+#else
+        switch (fputil::quick_get_round()) {
+        case FE_TONEAREST:
+        case FE_DOWNWARD:
+          return x;
+        default:
+          return FPBits(static_cast<uint16_t>(x_u - 1U)).get_val();
+        }
+#endif // LIBC_MATH_HAS_ASSUME_ROUND_NEAREST_ONLY
+      }
+
       float xf = x;
       float xf_sq = xf * xf;
       // Degree-7 Taylor expansion generated by Sollya with the following
diff --git a/libc/test/src/math/smoke/tanhf16_test.cpp b/libc/test/src/math/smoke/tanhf16_test.cpp
index e00cb0a53650cd..19b1726efc6076 100644
--- a/libc/test/src/math/smoke/tanhf16_test.cpp
+++ b/libc/test/src/math/smoke/tanhf16_test.cpp
@@ -15,6 +15,74 @@
 
 using LlvmLibcTanhf16Test = LIBC_NAMESPACE::testing::FPTest<float16>;
 
+TEST_F(LlvmLibcTanhf16Test, AllSubnormalRounding) {
+  using Bits = LIBC_NAMESPACE::fputil::FPBits<float16>;
+  using namespace LIBC_NAMESPACE::fputil::testing;
+#ifdef LIBC_MATH_HAS_ASSUME_ROUND_NEAREST_ONLY
+  constexpr RoundingMode MODES[] = {RoundingMode::Nearest};
+#else
+  constexpr RoundingMode MODES[] = {RoundingMode::Nearest, RoundingMode::Upward,
+                                    RoundingMode::Downward,
+                                    RoundingMode::TowardZero};
+#endif
+  for (RoundingMode mode : MODES) {
+    ForceRoundingMode rounding(mode);
+    if (!rounding.success)
+      continue;
+    for (uint16_t magnitude = 1; magnitude < 0x0400; ++magnitude) {
+      constexpr uint16_t SIGNS[] = {0, 0x8000};
+      for (uint16_t sign : SIGNS) {
+        bool negative = sign != 0;
+        uint16_t expected = static_cast<uint16_t>(magnitude | sign);
+        if ((!negative && (mode == RoundingMode::Downward ||
+                           mode == RoundingMode::TowardZero)) ||
+            (negative && (mode == RoundingMode::Upward ||
+                          mode == RoundingMode::TowardZero)))
+          --expected;
+        bool tiny = (expected & 0x7fff) < 0x0400;
+        EXPECT_FP_EQ_WITH_EXCEPTION(
+            Bits(expected).get_val(),
+            LIBC_NAMESPACE::tanhf16(Bits(uint16_t(magnitude | sign)).get_val()),
+            FE_INEXACT | (tiny ? FE_UNDERFLOW : 0));
+        EXPECT_MATH_ERRNO(tiny ? ERANGE : 0);
+      }
+    }
+  }
+}
+
+TEST_F(LlvmLibcTanhf16Test, MinNormalTininessAfterRounding) {
+  using Bits = LIBC_NAMESPACE::fputil::FPBits<float16>;
+  using namespace LIBC_NAMESPACE::fputil::testing;
+#ifdef LIBC_MATH_HAS_ASSUME_ROUND_NEAREST_ONLY
+  constexpr RoundingMode MODES[] = {RoundingMode::Nearest};
+#else
+  constexpr RoundingMode MODES[] = {RoundingMode::Nearest, RoundingMode::Upward,
+                                    RoundingMode::Downward,
+                                    RoundingMode::TowardZero};
+#endif
+  for (RoundingMode mode : MODES) {
+    ForceRoundingMode rounding(mode);
+    if (!rounding.success)
+      continue;
+    constexpr uint16_t SIGNS[] = {0, 0x8000};
+    for (uint16_t sign : SIGNS) {
+      bool negative = sign != 0;
+      uint16_t expected = static_cast<uint16_t>(0x0400 | sign);
+      if ((!negative && (mode == RoundingMode::Downward ||
+                         mode == RoundingMode::TowardZero)) ||
+          (negative &&
+           (mode == RoundingMode::Upward || mode == RoundingMode::TowardZero)))
+        --expected;
+      bool tiny = (expected & 0x7fff) < 0x0400;
+      EXPECT_FP_EQ_WITH_EXCEPTION(
+          Bits(expected).get_val(),
+          LIBC_NAMESPACE::tanhf16(Bits(uint16_t(0x0400 | sign)).get_val()),
+          FE_INEXACT | (tiny ? FE_UNDERFLOW : 0));
+      EXPECT_MATH_ERRNO(tiny ? ERANGE : 0);
+    }
+  }
+}
+
 TEST_F(LlvmLibcTanhf16Test, SpecialNumbers) {
   EXPECT_FP_EQ_ALL_ROUNDING(aNaN, LIBC_NAMESPACE::tanhf16(aNaN));
   EXPECT_MATH_ERRNO(0);



More information about the libc-commits mailing list