Skip to content

Commit 9a7d997

Browse files
fix(math): nextafter/nextafterf raise FE_OVERFLOW/FE_UNDERFLOW (glibc parity)
Differential probe of over/underflow exception cases found nextafter/nextafterf omit the C99/IEEE F.10.8.3 flags glibc raises (scalbn/ldexp were already correct): nextafter(MAX, INF) -> FE_OVERFLOW (finite x, infinite result) nextafter(0, ±1) -> FE_UNDERFLOW (result smallest subnormal) nextafter(MIN_POSITIVE, 0) -> FE_UNDERFLOW (result subnormal) (+ f32 nextafterf, same) Per the spec: nextafter raises OVERFLOW(+INEXACT) when x is finite and the result is infinite, and UNDERFLOW(+INEXACT) when the result is subnormal or zero and differs from x. libm returns the correct value but omits the flags. Fix: re-raise via a hardware op (MAX*MAX -> inf+OVERFLOW; MIN_POSITIVE^2 -> 0+UNDERFLOW) on the boundary results only; x==y and normal results raise nothing. Gate: conformance_diff_fp_exceptions extended with the nextafter cases (flags exact vs host glibc). Value unchanged (libm's result returned as-is). All 155 math lib tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 563d99d commit 9a7d997

3 files changed

Lines changed: 43 additions & 2 deletions

File tree

crates/frankenlibc-abi/tests/conformance_diff_fp_exceptions.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ unsafe extern "C" {
1717
fn acosh(x: f64) -> f64; fn atanh(x: f64) -> f64; fn tgamma(x: f64) -> f64; fn fmod(x: f64, y: f64) -> f64;
1818
fn logf(x: f32)->f32; fn log2f(x: f32)->f32; fn log10f(x: f32)->f32; fn sqrtf(x: f32)->f32;
1919
fn acosf(x: f32)->f32; fn acoshf(x: f32)->f32; fn tgammaf(x: f32)->f32; fn powf(x: f32,y: f32)->f32;
20+
fn nextafter(x: f64, y: f64) -> f64; fn nextafterf(x: f32, y: f32) -> f32;
2021
}
2122
const HARD: c_int = 0x1D; // INVALID|DIVBYZERO|OVERFLOW|UNDERFLOW (drop noisy INEXACT)
2223
fn key(x: f64) -> i64 { let b = x.to_bits() as i64; if b < 0 { i64::MIN - b } else { b } }
@@ -89,5 +90,23 @@ fn fp_exception_and_value_parity_vs_glibc() {
8990
chkf!("tgammaf(-1)", fl::tgammaf(-1.0), tgammaf(-1.0));
9091
chkf!("tgammaf(-5)", fl::tgammaf(-5.0), tgammaf(-5.0));
9192
chkf!("powf(0,-1)", fl::powf(0.0,-1.0), powf(0.0,-1.0));
93+
94+
// nextafter / nextafterf: C99 F.10.8.3 OVERFLOW (finite->inf) + UNDERFLOW
95+
// (result subnormal/zero). The flag-only macros reuse chkf; for f64 wrap via
96+
// a closure-free direct call.
97+
macro_rules! chk2 { ($lbl:literal, $flf:expr, $gf:expr) => {{
98+
unsafe { feclearexcept(HARD); } let _ = std::hint::black_box($flf); let ff = unsafe { fetestexcept(HARD) };
99+
unsafe { feclearexcept(HARD); } let _ = std::hint::black_box(unsafe { $gf }); let gf = unsafe { fetestexcept(HARD) };
100+
if (ff & HARD) != (gf & HARD) { div.push(format!("{} flags: fl={:#x} glibc={:#x}", $lbl, ff & HARD, gf & HARD)); }
101+
}}; }
102+
chk2!("nextafter(0,1)", fl::nextafter(0.0,1.0), nextafter(0.0,1.0));
103+
chk2!("nextafter(0,-1)", fl::nextafter(0.0,-1.0), nextafter(0.0,-1.0));
104+
chk2!("nextafter(MAX,INF)", fl::nextafter(f64::MAX,f64::INFINITY), nextafter(f64::MAX,f64::INFINITY));
105+
chk2!("nextafter(minpos,0)", fl::nextafter(f64::MIN_POSITIVE,0.0), nextafter(f64::MIN_POSITIVE,0.0));
106+
chk2!("nextafter(5,5)", fl::nextafter(5.0,5.0), nextafter(5.0,5.0));
107+
chk2!("nextafter(1,2)", fl::nextafter(1.0,2.0), nextafter(1.0,2.0));
108+
chk2!("nextafterf(0,1)", fl::nextafterf(0.0,1.0), nextafterf(0.0,1.0));
109+
chk2!("nextafterf(MAX,INF)", fl::nextafterf(f32::MAX,f32::INFINITY), nextafterf(f32::MAX,f32::INFINITY));
110+
chk2!("nextafterf(minpos,0)", fl::nextafterf(f32::MIN_POSITIVE,0.0), nextafterf(f32::MIN_POSITIVE,0.0));
92111
assert!(div.is_empty(), "fp-exception/value divergences vs glibc:\n {}", div.join("\n "));
93112
}

crates/frankenlibc-core/src/math/float.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,19 @@ pub fn scalbln(x: f64, n: i64) -> f64 {
174174
/// Return the next representable float after `x` toward `y`.
175175
#[inline]
176176
pub fn nextafter(x: f64, y: f64) -> f64 {
177-
libm::nextafter(x, y)
177+
let r = libm::nextafter(x, y);
178+
// C99/IEEE F.10.8.3: nextafter raises FE_OVERFLOW (+INEXACT) when x is finite
179+
// and the result is infinite, and FE_UNDERFLOW (+INEXACT) when the result is
180+
// subnormal or zero (and differs from x). libm omits these flags; re-raise via
181+
// a hardware op (safe; only on these boundary results).
182+
if x.is_finite() && r.is_infinite() {
183+
let _ = core::hint::black_box(core::hint::black_box(f64::MAX) * core::hint::black_box(f64::MAX));
184+
} else if r != x && r.abs() < f64::MIN_POSITIVE {
185+
let _ = core::hint::black_box(
186+
core::hint::black_box(f64::MIN_POSITIVE) * core::hint::black_box(f64::MIN_POSITIVE),
187+
);
188+
}
189+
r
178190
}
179191

180192
/// Return the next representable `f64` after `x` toward `y` (long double direction).

crates/frankenlibc-core/src/math/float32.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -577,7 +577,17 @@ pub fn scalblnf(x: f32, n: i64) -> f32 {
577577

578578
#[inline]
579579
pub fn nextafterf(x: f32, y: f32) -> f32 {
580-
libm::nextafterf(x, y)
580+
let r = libm::nextafterf(x, y);
581+
// C99/IEEE: FE_OVERFLOW when finite -> infinite; FE_UNDERFLOW when the result
582+
// is subnormal/zero and differs from x. libm omits these; re-raise.
583+
if x.is_finite() && r.is_infinite() {
584+
let _ = core::hint::black_box(core::hint::black_box(f32::MAX) * core::hint::black_box(f32::MAX));
585+
} else if r != x && r.abs() < f32::MIN_POSITIVE {
586+
let _ = core::hint::black_box(
587+
core::hint::black_box(f32::MIN_POSITIVE) * core::hint::black_box(f32::MIN_POSITIVE),
588+
);
589+
}
590+
r
581591
}
582592

583593
/// Return the next representable `f32` after `x` toward `y` (long double direction).

0 commit comments

Comments
 (0)