[libc-commits] [libc] [libc][math] Short-circuit certain powf range errors (PR #223719)
via libc-commits
libc-commits at lists.llvm.org
Fri Oct 9 09:49:13 PDT 2026
https://github.com/sriramshastry updated https://github.com/llvm/llvm-project/pull/223719
>From 28685b2da514cbd7a788c3cc2482c2f80b0a91cc Mon Sep 17 00:00:00 2001
From: sriramshastry <sriramshastry at gmail.com>
Date: Fri, 9 Oct 2026 16:21:13 +0000
Subject: [PATCH] [libc][math] Short-circuit provable powf subnormal underflow
For a nonzero subnormal base, reuse the normalization shift to bound
its magnitude below 2^(-125-shift). For positive y, y*(125+shift) >= 150
proves the result is strictly below half the minimum binary32 subnormal.
Return through the existing signed underflow helper after special-case
and domain handling.
Use an exact double product when hardware double is available, and an
exact unsigned significand comparison otherwise. Add rounding-mode and
threshold-neighbor smoke coverage for both evaluators.
The redesigned implementation already handles extreme exponents, so do
not retain the old large-exponent shortcut. Performance remains workload
and layout dependent; this is not a uniform no-regression claim.
Assisted-by: GitHub Copilot
Signed-off-by: sriramshastry <sriramshastry at gmail.com>
---
libc/src/__support/math/powf_utils.h | 13 +++++++++
libc/test/src/math/smoke/powf_test.cpp | 40 ++++++++++++++++++++++++++
2 files changed, 53 insertions(+)
diff --git a/libc/src/__support/math/powf_utils.h b/libc/src/__support/math/powf_utils.h
index 2a2253bd273796..ac74b851d44ea7 100644
--- a/libc/src/__support/math/powf_utils.h
+++ b/libc/src/__support/math/powf_utils.h
@@ -598,6 +598,19 @@ check_exceptional_cases(float &x, float &y, int &ex, SignType &sign) {
// Normalize denormal inputs.
if (x_a < FloatBits::min_normal().uintval()) {
int shift = cpp::countl_zero(x_a) - 8;
+#ifdef LIBC_TARGET_CPU_HAS_FPU_DOUBLE
+ if (!y_sign &&
+ static_cast<double>(y) * static_cast<double>(125 + shift) >= 150.0)
+ return set_underflow(sign == 0 ? Sign::POS : Sign::NEG);
+#else
+ uint32_t log2_bound = static_cast<uint32_t>(125 + shift);
+ if (!y_sign &&
+ (y_a >= 0x4000'0000U || (y_a >= FloatBits::one().uintval() &&
+ (y_a - 0x3f00'0000U) * log2_bound >=
+ (150U << FloatBits::FRACTION_LEN))))
+ return set_underflow(sign == 0 ? Sign::POS : Sign::NEG);
+#endif
+
ex -= shift;
x = cpp::bit_cast<float>(x_a << shift);
}
diff --git a/libc/test/src/math/smoke/powf_test.cpp b/libc/test/src/math/smoke/powf_test.cpp
index 46d4f1b7120944..0b07abe78aebba 100644
--- a/libc/test/src/math/smoke/powf_test.cpp
+++ b/libc/test/src/math/smoke/powf_test.cpp
@@ -247,6 +247,43 @@ class PowfTest : public LIBC_NAMESPACE::testing::FPTest<float> {
}
}
+ void test_range_underflow(float (*func)(float, float)) {
+ constexpr float BASES[] = {0x1.8p-2f, 0x1.fffffep-2f, 0x1.8p-130f};
+ constexpr float EXPONENTS[] = {150.0f, 150.0f, 1.25f};
+ for (int mode = 0; mode < N_ROUNDING_MODES; ++mode) {
+ ForceRoundingMode rounding(ROUNDING_MODES[mode]);
+ if (!rounding.success)
+ continue;
+ float positive =
+ ROUNDING_MODES[mode] == RoundingMode::Upward ? min_denormal : zero;
+ float negative = ROUNDING_MODES[mode] == RoundingMode::Downward
+ ? -min_denormal
+ : neg_zero;
+ for (unsigned index = 0; index < 3; ++index)
+ EXPECT_FP_EQ_WITH_EXCEPTION(positive,
+ func(BASES[index], EXPONENTS[index]),
+ FE_UNDERFLOW | FE_INEXACT);
+ EXPECT_FP_EQ_WITH_EXCEPTION(negative, func(-0x1.8p-2f, 151.0f),
+ FE_UNDERFLOW | FE_INEXACT);
+ EXPECT_FP_EQ_WITH_EXCEPTION(negative, func(-0x1.8p-130f, 3.0f),
+ FE_UNDERFLOW | FE_INEXACT);
+ float boundary = ROUNDING_MODES[mode] == RoundingMode::Upward ||
+ ROUNDING_MODES[mode] == RoundingMode::Nearest
+ ? min_denormal
+ : zero;
+ EXPECT_FP_EQ_WITH_EXCEPTION(boundary, func(0x1.fffffep-2f, 0x1.2bfffep7f),
+ FE_UNDERFLOW | FE_INEXACT);
+ constexpr float SUBNORMAL_BOUND = 150.0f / 126.0f;
+ uint32_t bound_bits = FPBits(SUBNORMAL_BOUND).uintval();
+ EXPECT_FP_EQ_WITH_EXCEPTION(
+ boundary, func(0x1.fffffcp-127f, FPBits(bound_bits - 1).get_val()),
+ FE_UNDERFLOW | FE_INEXACT);
+ EXPECT_FP_EQ_WITH_EXCEPTION(
+ positive, func(0x1.fffffcp-127f, FPBits(bound_bits + 1).get_val()),
+ FE_UNDERFLOW | FE_INEXACT);
+ }
+ }
+
#ifdef LIBC_TEST_FTZ_DAZ
void test_ftz(float (*func)(float, float)) {
LIBC_NAMESPACE::testing::ModifyMXCSR mxcsr(LIBC_NAMESPACE::testing::FTZ);
@@ -298,6 +335,9 @@ class PowfTest : public LIBC_NAMESPACE::testing::FPTest<float> {
TEST_F(LlvmLibcPowfTest##suffix, SubnormalBase) { \
test_subnormal_base(&func, tolerance); \
} \
+ TEST_F(LlvmLibcPowfTest##suffix, RangeUnderflow) { \
+ test_range_underflow(&func); \
+ } \
LIST_POWF_FTZ_DAZ_TESTS(suffix, func) \
static_assert(true, "Require semicolon.")
More information about the libc-commits
mailing list