[libc-commits] [libc] [libc][math] Correct expm1f16 subnormal rounding and range reporting (PR #236310)
via libc-commits
libc-commits at lists.llvm.org
Fri Oct 30 07:54:19 PDT 2026
https://github.com/sriramshastry updated https://github.com/llvm/llvm-project/pull/236310
>From 087bd2de91990ccb2800d6aa6594481960def4fd Mon Sep 17 00:00:00 2001
From: sriramshastry <sriramshastry at gmail.com>
Date: Wed, 28 Oct 2026 09:16:36 +0530
Subject: [PATCH] [libc][math] Correct expm1f16 subnormal rounding and range
reporting
For nonzero binary16 subnormal inputs, expm1(x)-x is positive and less than
half a subnormal ULP. Select the correctly rounded result from x and its
adjacent value, preserving signed zero and directed rounding.
Report underflow and ERANGE according to the rounded result, including the
transition at negative minimum normal. Add exhaustive signed-subnormal and
negative-minimum-normal rounding-mode regressions.
MPFR's mpfr_subnormalize underflow flag differs from this tininess-after-
rounding policy at 0x03ff under upward rounding; see the PR description and
the associated validation results.
Signed-off-by: sriramshastry <sriramshastry at gmail.com>
---
libc/src/__support/math/expm1f16.h | 20 +++++++
libc/test/src/math/smoke/expm1f16_test.cpp | 61 ++++++++++++++++++++++
2 files changed, 81 insertions(+)
diff --git a/libc/src/__support/math/expm1f16.h b/libc/src/__support/math/expm1f16.h
index 08234a0ad4ace9..072ead28957f71 100644
--- a/libc/src/__support/math/expm1f16.h
+++ b/libc/src/__support/math/expm1f16.h
@@ -120,6 +120,26 @@ LIBC_INLINE constexpr float16 expm1f16(float16 x) {
// When 0 < |x| <= 2^(-3).
if (x_abs <= 0x3000U && !x_bits.is_zero()) {
+ // 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 || x_u == 0x8400U)) {
+ uint16_t out = x_u;
+#ifndef LIBC_MATH_HAS_ASSUME_ROUND_NEAREST_ONLY
+ int mode = fputil::quick_get_round();
+ bool increase =
+ (x_bits.is_pos() && mode == FE_UPWARD) ||
+ (x_bits.is_neg() && (mode == FE_UPWARD || mode == FE_TOWARDZERO));
+ if (increase)
+ out = static_cast<uint16_t>(x_u + (x_bits.is_neg() ? -1 : 1));
+#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();
+ }
#ifndef LIBC_MATH_HAS_SKIP_ACCURATE_PASS
if (auto r = EXPM1F16_EXCEPTS_LO.lookup(x_u);
diff --git a/libc/test/src/math/smoke/expm1f16_test.cpp b/libc/test/src/math/smoke/expm1f16_test.cpp
index 545304ced63ed8..2a136bf6b22282 100644
--- a/libc/test/src/math/smoke/expm1f16_test.cpp
+++ b/libc/test/src/math/smoke/expm1f16_test.cpp
@@ -15,6 +15,67 @@
using LlvmLibcExpm1f16Test = LIBC_NAMESPACE::testing::FPTest<float16>;
+TEST_F(LlvmLibcExpm1f16Test, 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::Upward) ||
+ (negative && (mode == RoundingMode::Upward ||
+ mode == RoundingMode::TowardZero)))
+ expected = static_cast<uint16_t>(expected + (negative ? -1 : 1));
+ bool tiny = (expected & 0x7fff) < 0x0400;
+ EXPECT_FP_EQ_WITH_EXCEPTION(
+ Bits(expected).get_val(),
+ LIBC_NAMESPACE::expm1f16(
+ Bits(uint16_t(magnitude | sign)).get_val()),
+ FE_INEXACT | (tiny ? FE_UNDERFLOW : 0));
+ EXPECT_MATH_ERRNO(tiny ? ERANGE : 0);
+ }
+ }
+ }
+}
+
+TEST_F(LlvmLibcExpm1f16Test, NegativeMinNormalTininessAfterRounding) {
+ 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;
+ uint16_t expected = 0x8400;
+ if (mode == RoundingMode::Upward || mode == RoundingMode::TowardZero)
+ expected = 0x83ff;
+ bool tiny = (expected & 0x7fff) < 0x0400;
+ EXPECT_FP_EQ_WITH_EXCEPTION(
+ Bits(expected).get_val(),
+ LIBC_NAMESPACE::expm1f16(Bits(uint16_t(0x8400)).get_val()),
+ FE_INEXACT | (tiny ? FE_UNDERFLOW : 0));
+ EXPECT_MATH_ERRNO(tiny ? ERANGE : 0);
+ }
+}
+
TEST_F(LlvmLibcExpm1f16Test, SpecialNumbers) {
EXPECT_FP_EQ_ALL_ROUNDING(aNaN, LIBC_NAMESPACE::expm1f16(aNaN));
EXPECT_MATH_ERRNO(0);
More information about the libc-commits
mailing list