[libc-commits] [libc] [libc][math] Correct logb exceptions and nextup/down boundaries (PR #236473)
via libc-commits
libc-commits at lists.llvm.org
Wed Oct 28 20:25:39 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-libc
Author: sriramshastry
<details>
<summary>Changes</summary>
Keep the logb and nextup/down fixes as two separate commits. Both change
FPUtil/ManipulationFunctions.h; one review branch avoids cross-PR file overlap.
PR #<!-- -->236310 and the expm1f16 subnormal patch are not included.
## Group validation
Tested on upstream ea08830d976fbe61384695d33e0acafee8d88737, x86-64 Linux, Release,
with GCC 13 and Clang 18. The exact two-patch group passes 36 native target
configurations per compiler. This covers logb special inputs and binary80
nextup/down boundaries. No all-platform or performance claim is made.
## Commit 1: Correct logb exception handling for infinities and signaling NaNs
Raise FE_DIVBYZERO only for zero inputs to logb. Infinity inputs should return positive
infinity without that exception. Handle signaling NaNs by raising FE_INVALID and
returning a quiet NaN. Extend the shared smoke tests across all six enabled precisions.
### Validation
Tested independently at ea08830d976fbe61384695d33e0acafee8d88737 on x86-64 Linux,
Release, GCC 13 and Clang 18. With only the revised tests, 12/12 smoke target
configurations fail per compiler; with the repair, 12/12 pass. The regression checks
flags and errno for zero, infinity, NaNs, and valid finite inputs, and explicitly
rejects a signaling NaN result.
### Scope and Review
Shares ManipulationFunctions.h with the separate nextup/nextdown-boundary proposal.
Coordinate with open Float128 exponent-decomposition work, including PR #<!-- -->221540. The
test explicitly checks NaN quieting.
The inspected file list for #<!-- -->221540 does not change this helper or its smoke fixture.
This is a logb exception repair, not an ilogb contract change.
## Commit 2: Fix nextup and nextdown at finite range boundaries
Let nextup and nextdown step from the last finite value to infinity. Correct the x86
extended precision normal and subnormal transitions, which also change the explicit
integer bit. Update the shared smoke fixtures to check these boundaries and require no
floating point exceptions.
### Validation
Tested independently at ea08830d976fbe61384695d33e0acafee8d88737 on x86-64 Linux,
Release, GCC 13 and Clang 18. With only the revised tests, 24/24 smoke target
configurations fail per compiler; with the repair, 24/24 pass. Binary80 normal/subnormal
transitions and the absence of nextafter-style range flags are covered.
### Scope and Review
Review binary80 explicit-integer-bit transitions. nextup/nextdown must not inherit
nextafter range exceptions. Shares ManipulationFunctions.h with the separate logb
exception proposal.
## Updated submission base
Submission base: 10c142fa799e258285b6cfb8fce153864586152d.
The standalone baseline/patched results above remain recorded at their original
tested snapshot. At this newer base, the fourteen-only combined suite passes
150 native configurations per compiler and twelve four-mode numerical runs,
with zero value or scoped-reporting mismatches.
The exact two-commit group also passes 36 native configurations per compiler
at this newer base.
Signed-off-by: sriramshastry <sriramshastry@<!-- -->gmail.com>
---
Full diff: https://github.com/llvm/llvm-project/pull/236473.diff
5 Files Affected:
- (modified) libc/src/__support/FPUtil/ManipulationFunctions.h (+9-6)
- (modified) libc/src/__support/FPUtil/x86_64/NextUpDownLongDouble.h (+9-3)
- (modified) libc/test/src/math/smoke/LogbTest.h (+14-5)
- (modified) libc/test/src/math/smoke/NextDownTest.h (+8-2)
- (modified) libc/test/src/math/smoke/NextUpTest.h (+8-2)
``````````diff
diff --git a/libc/src/__support/FPUtil/ManipulationFunctions.h b/libc/src/__support/FPUtil/ManipulationFunctions.h
index 02dded2f47913c..e4880bc84e3f46 100644
--- a/libc/src/__support/FPUtil/ManipulationFunctions.h
+++ b/libc/src/__support/FPUtil/ManipulationFunctions.h
@@ -135,13 +135,17 @@ template <typename T, cpp::enable_if_t<cpp::is_floating_point_v<T>, int> = 0>
LIBC_INLINE constexpr T logb(T x) {
FPBits<T> bits(x);
if (LIBC_UNLIKELY(bits.is_zero() || bits.is_inf_or_nan())) {
- if (bits.is_nan())
+ if (bits.is_nan()) {
+ if (bits.is_signaling_nan()) {
+ raise_except_if_required(FE_INVALID);
+ return FPBits<T>::quiet_nan().get_val();
+ }
return x;
-
- raise_except_if_required(FE_DIVBYZERO);
+ }
if (bits.is_zero()) {
set_errno_if_required(ERANGE);
+ raise_except_if_required(FE_DIVBYZERO);
return FPBits<T>::inf(Sign::NEG).get_val();
}
// bits is inf.
@@ -254,12 +258,11 @@ LIBC_INLINE constexpr T nextupdown(T x) {
constexpr Sign sign = IsDown ? Sign::NEG : Sign::POS;
FPBits<T> xbits(x);
- if (xbits.is_nan() || xbits == FPBits<T>::max_normal(sign) ||
- xbits == FPBits<T>::inf(sign))
+ if (xbits.is_nan() || xbits == FPBits<T>::inf(sign))
return x;
using StorageType = typename FPBits<T>::StorageType;
- if (x != T(0)) {
+ if (!xbits.is_zero()) {
if (xbits.sign() == sign) {
xbits = FPBits<T>(StorageType(xbits.uintval() + 1));
} else {
diff --git a/libc/src/__support/FPUtil/x86_64/NextUpDownLongDouble.h b/libc/src/__support/FPUtil/x86_64/NextUpDownLongDouble.h
index 589328ae2a425a..61a7cea8c5fbbb 100644
--- a/libc/src/__support/FPUtil/x86_64/NextUpDownLongDouble.h
+++ b/libc/src/__support/FPUtil/x86_64/NextUpDownLongDouble.h
@@ -27,11 +27,17 @@ LIBC_INLINE LIBC_BIT_CAST_CONSTEXPR long double nextupdown(long double x) {
using FPBits_t = FPBits<long double>;
FPBits_t xbits(x);
- if (xbits.is_nan() || xbits == FPBits_t::max_normal(sign) ||
- xbits == FPBits_t::inf(sign))
+ if (xbits.is_nan() || xbits == FPBits_t::inf(sign))
return x;
- if (x == 0.0l)
+ // Crossing the normal/subnormal boundary also changes the explicit
+ // integer bit in the x86 extended precision encoding.
+ if (xbits == FPBits_t::max_subnormal(sign))
+ return FPBits_t::min_normal(sign).get_val();
+ if (xbits == FPBits_t::min_normal(sign.negate()))
+ return FPBits_t::max_subnormal(sign.negate()).get_val();
+
+ if (xbits.is_zero())
return FPBits_t::min_subnormal(sign).get_val();
using StorageType = typename FPBits_t::StorageType;
diff --git a/libc/test/src/math/smoke/LogbTest.h b/libc/test/src/math/smoke/LogbTest.h
index 3162af735c3747..e148f8888300cb 100644
--- a/libc/test/src/math/smoke/LogbTest.h
+++ b/libc/test/src/math/smoke/LogbTest.h
@@ -24,11 +24,20 @@ class LogbTest : public LIBC_NAMESPACE::testing::FEnvSafeTest {
typedef T (*LogbFunc)(T);
void testSpecialNumbers(LogbFunc func) {
- ASSERT_FP_EQ(aNaN, func(aNaN));
- ASSERT_FP_EQ(inf, func(inf));
- ASSERT_FP_EQ(inf, func(neg_inf));
- ASSERT_FP_EQ(neg_inf, func(zero));
- ASSERT_FP_EQ(neg_inf, func(neg_zero));
+ EXPECT_FP_EQ_WITH_EXCEPTION(aNaN, func(aNaN), 0);
+ EXPECT_MATH_ERRNO(0);
+ T snan_result = zero;
+ EXPECT_FP_EQ_WITH_EXCEPTION(aNaN, snan_result = func(sNaN), FE_INVALID);
+ EXPECT_FALSE(FPBits(snan_result).is_signaling_nan());
+ EXPECT_MATH_ERRNO(0);
+ EXPECT_FP_EQ_WITH_EXCEPTION(inf, func(inf), 0);
+ EXPECT_MATH_ERRNO(0);
+ EXPECT_FP_EQ_WITH_EXCEPTION(inf, func(neg_inf), 0);
+ EXPECT_MATH_ERRNO(0);
+ EXPECT_FP_EQ_WITH_EXCEPTION(neg_inf, func(zero), FE_DIVBYZERO);
+ EXPECT_MATH_ERRNO(ERANGE);
+ EXPECT_FP_EQ_WITH_EXCEPTION(neg_inf, func(neg_zero), FE_DIVBYZERO);
+ EXPECT_MATH_ERRNO(ERANGE);
}
void testPowersOfTwo(LogbFunc func) {
diff --git a/libc/test/src/math/smoke/NextDownTest.h b/libc/test/src/math/smoke/NextDownTest.h
index 0b0ab83bcb6b6a..039d246a2621ca 100644
--- a/libc/test/src/math/smoke/NextDownTest.h
+++ b/libc/test/src/math/smoke/NextDownTest.h
@@ -24,15 +24,21 @@ class NextDownTestTemplate : public LIBC_NAMESPACE::testing::FEnvSafeTest {
void testNaN(NextDownFunc func) { ASSERT_FP_EQ(func(aNaN), aNaN); }
void testBoundaries(NextDownFunc func) {
- ASSERT_FP_EQ(zero, func(min_denormal));
+ EXPECT_FP_EQ_WITH_EXCEPTION(zero, func(min_denormal), 0);
ASSERT_FP_EQ(neg_min_denormal, func(zero));
ASSERT_FP_EQ(neg_min_denormal, func(neg_zero));
- ASSERT_FP_EQ(neg_max_normal, func(neg_max_normal));
+ EXPECT_FP_EQ_WITH_EXCEPTION(neg_inf, func(neg_max_normal), 0);
ASSERT_FP_EQ(neg_inf, func(neg_inf));
ASSERT_FP_EQ(max_normal, func(inf));
+
+ EXPECT_FP_EQ_WITH_EXCEPTION(
+ FPBits::min_normal(Sign::NEG).get_val(),
+ func(FPBits::max_subnormal(Sign::NEG).get_val()), 0);
+ EXPECT_FP_EQ_WITH_EXCEPTION(FPBits::max_subnormal().get_val(),
+ func(FPBits::min_normal().get_val()), 0);
}
};
diff --git a/libc/test/src/math/smoke/NextUpTest.h b/libc/test/src/math/smoke/NextUpTest.h
index 886be60e595726..46eb69c808e020 100644
--- a/libc/test/src/math/smoke/NextUpTest.h
+++ b/libc/test/src/math/smoke/NextUpTest.h
@@ -24,15 +24,21 @@ class NextUpTestTemplate : public LIBC_NAMESPACE::testing::FEnvSafeTest {
void testNaN(NextUpFunc func) { ASSERT_FP_EQ(func(aNaN), aNaN); }
void testBoundaries(NextUpFunc func) {
- ASSERT_FP_EQ(neg_zero, func(neg_min_denormal));
+ EXPECT_FP_EQ_WITH_EXCEPTION(neg_zero, func(neg_min_denormal), 0);
ASSERT_FP_EQ(min_denormal, func(zero));
ASSERT_FP_EQ(min_denormal, func(neg_zero));
- ASSERT_FP_EQ(max_normal, func(max_normal));
+ EXPECT_FP_EQ_WITH_EXCEPTION(inf, func(max_normal), 0);
ASSERT_FP_EQ(inf, func(inf));
ASSERT_FP_EQ(neg_max_normal, func(neg_inf));
+
+ EXPECT_FP_EQ_WITH_EXCEPTION(FPBits::min_normal().get_val(),
+ func(FPBits::max_subnormal().get_val()), 0);
+ EXPECT_FP_EQ_WITH_EXCEPTION(FPBits::max_subnormal(Sign::NEG).get_val(),
+ func(FPBits::min_normal(Sign::NEG).get_val()),
+ 0);
}
};
``````````
</details>
https://github.com/llvm/llvm-project/pull/236473
More information about the libc-commits
mailing list