[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