Skip to content

Commit 40b7193

Browse files
fix(math): errno parity — logb no errno, nextafter/nexttoward range, significand EDOM
Continued differential probe of the math ABI errno layer vs host glibc (gcc -lm oracle) found four more divergences: - logb/logbf(0) wrongly set ERANGE; glibc raises only the FE_DIVBYZERO flag (handled in core) and leaves errno untouched. Removed the errno store. - nextafter/nextafterf/nexttoward/nexttowardf set no errno; glibc raises ERANGE on overflow (finite -> infinite) and on underflow (magnitude decreases into a subnormal/zero result). nextafter(0,y) growing to the smallest subnormal is NOT an underflow, so it stays errno-free. Added nextafter_range_error_f64/f32 helper encoding that exact rule. - significand/significandf(0) set no errno; glibc reports EDOM (0 has no normalized mantissa). Added the domain check. conformance_math_errno extended with 20+ cases (overflow, underflow, the no-error growth case, logb, significand, drem). Gate green; 114 math_abi unit + 155 core math tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent ec076cb commit 40b7193

2 files changed

Lines changed: 88 additions & 12 deletions

File tree

crates/frankenlibc-abi/src/math_abi.rs

Lines changed: 60 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,34 @@ fn set_range_errno() {
4545
unsafe { set_abi_errno(libc::ERANGE) };
4646
}
4747

48+
/// nextafter/nexttoward range-error rule, matching glibc: ERANGE on overflow
49+
/// (finite x -> infinite result) and on underflow (result subnormal-or-zero AND
50+
/// magnitude decreased). nextafter(0, y) -> smallest subnormal is NOT an
51+
/// underflow (magnitude increased from 0), so it sets no errno.
52+
#[inline]
53+
fn nextafter_range_error_f64(x: f64, r: f64) -> bool {
54+
if x.is_nan() || r.is_nan() {
55+
return false;
56+
}
57+
if x.is_finite() && r.is_infinite() {
58+
return true; // overflow
59+
}
60+
let sub_or_zero = r == 0.0 || r.abs() < f64::MIN_POSITIVE;
61+
sub_or_zero && r.abs() < x.abs() // underflow (magnitude decreased)
62+
}
63+
64+
#[inline]
65+
fn nextafter_range_error_f32(x: f32, r: f32) -> bool {
66+
if x.is_nan() || r.is_nan() {
67+
return false;
68+
}
69+
if x.is_finite() && r.is_infinite() {
70+
return true;
71+
}
72+
let sub_or_zero = r == 0.0 || r.abs() < f32::MIN_POSITIVE;
73+
sub_or_zero && r.abs() < x.abs()
74+
}
75+
4876
#[inline]
4977
fn scaling_range_error_f64(x: f64, out: f64) -> bool {
5078
x.is_finite() && x != 0.0 && (out.is_infinite() || out == 0.0)
@@ -613,7 +641,11 @@ pub unsafe extern "C" fn scalbln(x: f64, n: i64) -> f64 {
613641

614642
#[cfg_attr(not(debug_assertions), unsafe(no_mangle))]
615643
pub unsafe extern "C" fn nextafter(x: f64, y: f64) -> f64 {
616-
frankenlibc_core::math::nextafter(x, y)
644+
let out = frankenlibc_core::math::nextafter(x, y);
645+
if nextafter_range_error_f64(x, out) {
646+
set_range_errno();
647+
}
648+
out
617649
}
618650

619651
/// C99 `nexttoward`: next representable f64 toward a long-double direction.
@@ -623,7 +655,11 @@ pub unsafe extern "C" fn nextafter(x: f64, y: f64) -> f64 {
623655
unsafe(no_mangle)
624656
)]
625657
pub unsafe extern "C" fn nexttoward(x: f64, y: f64) -> f64 {
626-
frankenlibc_core::math::nexttoward(x, y)
658+
let out = frankenlibc_core::math::nexttoward(x, y);
659+
if nextafter_range_error_f64(x, out) {
660+
set_range_errno();
661+
}
662+
out
627663
}
628664

629665
#[cfg_attr(not(debug_assertions), unsafe(no_mangle))]
@@ -633,9 +669,8 @@ pub unsafe extern "C" fn ilogb(x: f64) -> c_int {
633669

634670
#[cfg_attr(not(debug_assertions), unsafe(no_mangle))]
635671
pub unsafe extern "C" fn logb(x: f64) -> f64 {
636-
if x == 0.0 {
637-
set_range_errno(); // pole error: logb(0) = -Infinity
638-
}
672+
// glibc raises the FE_DIVBYZERO flag for logb(0) (handled in core) but does
673+
// NOT set errno — leave errno untouched to match.
639674
frankenlibc_core::math::logb(x)
640675
}
641676

@@ -811,6 +846,10 @@ pub unsafe extern "C" fn gamma(x: f64) -> f64 {
811846
/// Extract significand scaled to [1, 2).
812847
#[cfg_attr(not(debug_assertions), unsafe(no_mangle))]
813848
pub unsafe extern "C" fn significand(x: f64) -> f64 {
849+
// significand(0) has no normalized mantissa: glibc reports EDOM.
850+
if x == 0.0 {
851+
set_domain_errno();
852+
}
814853
frankenlibc_core::math::significand(x)
815854
}
816855

@@ -1159,11 +1198,8 @@ pub unsafe extern "C" fn log1pf(x: f32) -> f32 {
11591198

11601199
#[cfg_attr(not(debug_assertions), unsafe(no_mangle))]
11611200
pub unsafe extern "C" fn logbf(x: f32) -> f32 {
1162-
let out = unary_entry_f32(x, 4, frankenlibc_core::math::logbf);
1163-
if x == 0.0 {
1164-
set_range_errno();
1165-
}
1166-
out
1201+
// glibc does not set errno for logbf(0) (only the FE_DIVBYZERO flag).
1202+
unary_entry_f32(x, 4, frankenlibc_core::math::logbf)
11671203
}
11681204

11691205
// --- Special functions f32 ---
@@ -1405,7 +1441,11 @@ pub unsafe extern "C" fn scalblnf(x: f32, n: c_long) -> f32 {
14051441

14061442
#[cfg_attr(not(debug_assertions), unsafe(no_mangle))]
14071443
pub unsafe extern "C" fn nextafterf(x: f32, y: f32) -> f32 {
1408-
binary_entry_f32(x, y, 3, frankenlibc_core::math::nextafterf)
1444+
let out = binary_entry_f32(x, y, 3, frankenlibc_core::math::nextafterf);
1445+
if nextafter_range_error_f32(x, out) {
1446+
set_range_errno();
1447+
}
1448+
out
14091449
}
14101450

14111451
/// C99 `nexttowardf`: next representable f32 toward a long-double direction.
@@ -1414,7 +1454,11 @@ pub unsafe extern "C" fn nextafterf(x: f32, y: f32) -> f32 {
14141454
unsafe(no_mangle)
14151455
)]
14161456
pub unsafe extern "C" fn nexttowardf(x: f32, y: f64) -> f32 {
1417-
frankenlibc_core::math::nexttowardf(x, y)
1457+
let out = frankenlibc_core::math::nexttowardf(x, y);
1458+
if nextafter_range_error_f32(x, out) {
1459+
set_range_errno();
1460+
}
1461+
out
14181462
}
14191463

14201464
#[cfg(all(target_arch = "x86_64", any(not(debug_assertions), test)))]
@@ -1650,6 +1694,10 @@ pub unsafe extern "C" fn gammaf(x: f32) -> f32 {
16501694

16511695
#[cfg_attr(not(debug_assertions), unsafe(no_mangle))]
16521696
pub unsafe extern "C" fn significandf(x: f32) -> f32 {
1697+
// significand(0) has no normalized mantissa: glibc reports EDOM.
1698+
if x == 0.0 {
1699+
set_domain_errno();
1700+
}
16531701
unary_entry_f32(x, 3, frankenlibc_core::math::significandf)
16541702
}
16551703

crates/frankenlibc-abi/tests/conformance_math_errno.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,34 @@ fn math_errno_matches_glibc() {
119119
chk!("scalbn(1,-100000)", ERANGE, fa::scalbn(1.0, -100000));
120120
chk!("ldexp(1,-100000)", ERANGE, fa::ldexp(1.0, -100000));
121121

122+
// --- nextafter / nexttoward range errors (glibc rule) ---
123+
// overflow: finite -> infinite result
124+
chk!("nextafter(dmax,inf)", ERANGE, fa::nextafter(f64::MAX, f64::INFINITY));
125+
chk!("nextafter(-dmax,-inf)", ERANGE, fa::nextafter(-f64::MAX, f64::NEG_INFINITY));
126+
chk!("nextafterf(fmax,inf)", ERANGE, fa::nextafterf(f32::MAX, f32::INFINITY));
127+
chk!("nexttoward(dmax,inf)", ERANGE, fa::nexttoward(f64::MAX, f64::INFINITY));
128+
chk!("nexttowardf(fmax,inf)", ERANGE, fa::nexttowardf(f32::MAX, f64::INFINITY));
129+
// underflow: magnitude decreases into subnormal/zero
130+
chk!("nextafter(5e-324,0)", ERANGE, fa::nextafter(5e-324, 0.0));
131+
chk!("nextafter(min_norm,0)", ERANGE, fa::nextafter(2.2250738585072014e-308, 0.0));
132+
chk!("nexttoward(5e-324,0)", ERANGE, fa::nexttoward(5e-324, 0.0));
133+
// NOT a range error: nextafter(0, y) grows from 0 to smallest subnormal
134+
chk!("nextafter(0,1)", 0, fa::nextafter(0.0, 1.0));
135+
chk!("nextafter(1,2)", 0, fa::nextafter(1.0, 2.0));
136+
137+
// --- significand(0): EDOM (no normalized mantissa) ---
138+
chk!("significand(0)", EDOM, fa::significand(0.0));
139+
chk!("significandf(0)", EDOM, fa::significandf(0.0));
140+
141+
// --- logb(0): glibc raises FE flag only, errno stays 0 (NOT ERANGE) ---
142+
chk!("logb(0)", 0, fa::logb(0.0));
143+
chk!("logbf(0)", 0, fa::logbf(0.0));
144+
chk!("logb(inf)", 0, fa::logb(f64::INFINITY));
145+
146+
// --- drem domain errors ---
147+
chk!("drem(1,0)", EDOM, fa::drem(1.0, 0.0));
148+
chk!("drem(inf,1)", EDOM, fa::drem(f64::INFINITY, 1.0));
149+
122150
// --- Controls: no error ---
123151
chk!("sin(0.5)", 0, fa::sin(0.5));
124152
chk!("sqrt(4)", 0, fa::sqrt(4.0));

0 commit comments

Comments
 (0)