Skip to content

Commit d83b4c9

Browse files
fix(math): log1p/log1pf raise FE_DIVBYZERO at the x=-1 pole (glibc parity)
Differential probe of the pole cases I'd missed: log1p(-1) = log(0) = -inf is a pole where glibc raises FE_DIVBYZERO, but libm::log1p returns -inf without the flag (same omit-pattern as the earlier log/tgamma fixes). atanh(±1) poles and log1p(x<-1) domain errors were already correct (libm raises those). Fix: re-raise FE_DIVBYZERO when x == -1.0 via the force_eval idiom (-1.0/0.0 -> -inf + DIVBYZERO); the value is unchanged. f32 log1pf likewise. Gate: conformance_diff_fp_exceptions extended with the atanh/log1p pole+domain cases (incl. the verified-correct atanh poles and log1p domain, pinned). All 155 math lib tests pass; 0 divergences vs host glibc. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 724ea87 commit d83b4c9

3 files changed

Lines changed: 23 additions & 0 deletions

File tree

crates/frankenlibc-abi/tests/conformance_diff_fp_exceptions.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ unsafe extern "C" {
1919
fn acosf(x: f32)->f32; fn acoshf(x: f32)->f32; fn tgammaf(x: f32)->f32; fn powf(x: f32,y: f32)->f32;
2020
fn nextafter(x: f64, y: f64) -> f64; fn nextafterf(x: f32, y: f32) -> f32;
2121
fn lgamma(x: f64)->f64; fn lgammaf(x: f32)->f32; fn exp2(x: f64)->f64; fn expm1(x: f64)->f64;
22+
fn atanhf(x: f32)->f32; fn log1p(x: f64)->f64; fn log1pf(x: f32)->f32;
2223
}
2324
const HARD: c_int = 0x1D; // INVALID|DIVBYZERO|OVERFLOW|UNDERFLOW (drop noisy INEXACT)
2425
fn key(x: f64) -> i64 { let b = x.to_bits() as i64; if b < 0 { i64::MIN - b } else { b } }
@@ -124,5 +125,18 @@ fn fp_exception_and_value_parity_vs_glibc() {
124125
chk2!("expm1(1000)", fl::expm1(1000.0), expm1(1000.0));
125126
chk2!("pow(10,400)", fl::pow(10.0,400.0), pow(10.0,400.0));
126127
chk2!("pow(0.1,400)", fl::pow(0.1,400.0), pow(0.1,400.0));
128+
129+
// atanh / log1p poles (DIVBYZERO) + domain (INVALID): log1p(-1)/log1pf(-1)
130+
// were under-raising the pole flag (fixed); the rest pin currently-correct
131+
// behavior (libm raises atanh poles + log1p domain).
132+
chk2!("atanh(1)", fl::atanh(1.0), atanh(1.0));
133+
chk2!("atanh(-1)", fl::atanh(-1.0), atanh(-1.0));
134+
chk2!("atanh(2)", fl::atanh(2.0), atanh(2.0));
135+
chk2!("atanhf(1)", fl::atanhf(1.0), atanhf(1.0));
136+
chk2!("log1p(-1)", fl::log1p(-1.0), log1p(-1.0));
137+
chk2!("log1p(-2)", fl::log1p(-2.0), log1p(-2.0));
138+
chk2!("log1p(-1.5)", fl::log1p(-1.5), log1p(-1.5));
139+
chk2!("log1pf(-1)", fl::log1pf(-1.0), log1pf(-1.0));
140+
chk2!("log1pf(-2)", fl::log1pf(-2.0), log1pf(-2.0));
127141
assert!(div.is_empty(), "fp-exception/value divergences vs glibc:\n {}", div.join("\n "));
128142
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,11 @@ pub fn log10(x: f64) -> f64 {
481481

482482
#[inline]
483483
pub fn log1p(x: f64) -> f64 {
484+
// log1p(-1) = log(0) = -inf is a pole: glibc raises FE_DIVBYZERO, libm omits
485+
// it. (x < -1 domain errors raise FE_INVALID, which libm already does.)
486+
if x == -1.0 {
487+
let _ = core::hint::black_box(core::hint::black_box(-1.0_f64) / core::hint::black_box(0.0_f64));
488+
}
484489
libm::log1p(x)
485490
}
486491

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,10 @@ pub fn expm1f(x: f32) -> f32 {
427427

428428
#[inline]
429429
pub fn log1pf(x: f32) -> f32 {
430+
// log1pf(-1) = -inf is a pole: glibc raises FE_DIVBYZERO, libm omits it.
431+
if x == -1.0 {
432+
fe_divbyzero_f32();
433+
}
430434
libm::log1pf(x)
431435
}
432436

0 commit comments

Comments
 (0)