Skip to content

libm: fix acosh & acoshf for negative inputs#1070

Merged
tgross35 merged 2 commits intorust-lang:mainfrom
quaternic:acosh-bug
Feb 7, 2026
Merged

libm: fix acosh & acoshf for negative inputs#1070
tgross35 merged 2 commits intorust-lang:mainfrom
quaternic:acosh-bug

Conversation

@quaternic
Copy link
Contributor

@quaternic quaternic commented Feb 2, 2026

The acosh-functions were incorrectly returning finite values for some negative inputs (should be NaN for any x < 1.0)

The bug was inherited when originally ported from musl, and this patch follows their fix for single-precision acoshf in https://git.musl-libc.org/cgit/musl/commit/?id=c4c38e6364323b6d83ba3428464e19987b981d7a

Comment on lines -404 to -411
// FIXME(#939): this should not be skipped, there is a bug in our implementationi.
if ctx.base_name == BaseName::FmaximumNum
&& ctx.basis == CheckBasis::Mpfr
&& ((input.0.is_nan() && actual.is_nan() && expected.is_nan()) || input.1.is_nan())
{
return XFAIL_NOCHECK;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unrelated to acoshf, but was just a leftover from #939 which is already merged.

@quaternic quaternic changed the title libm: fix acoshf for negative inputs libm: fix acosh & acoshf for negative inputs Feb 3, 2026
tgross35 pushed a commit that referenced this pull request Feb 3, 2026
With the json target specification format destabilized in
rust-lang/rust#150151, `-Zjson-target-spec` is
needed for custom targets. This should resolve the CI failures seen in
#1070
@rustbot

This comment has been minimized.

return log1p(x - 1.0 + sqrt((x - 1.0) * (x - 1.0) + 2.0 * (x - 1.0)));
let x_1 = x - 1.0;
log1p(x_1 + sqrt(x_1 * x_1 + 2.0 * x_1))
} else if u < 0x3ff + 26 {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately the nonessential changes ended up hiding the actual bug fix in the diff, which is simply replacing

if e < 0x3ff + 26 {

with

if u < 0x3ff + 26 {

The difference is that u hasn't had the sign bit cleared, so negative inputs no longer take that branch. The fix for acoshf is equivalent.

@tgross35
Copy link
Contributor

tgross35 commented Feb 3, 2026

To clarify; musl fixed their acoshf but not acosh? And this PR fixes both for us?

Comment on lines 11 to 13
pub fn acosh(x: f64) -> f64 {
let u = x.to_bits();
let e = ((u >> 52) as usize) & 0x7ff;
let u = x.to_bits() >> 52;
let e = u & 0x7ff;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm assuming the as usize cast gave slightly better codegen on 32-bit systems, was it problematic?

Copy link
Contributor Author

@quaternic quaternic Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, that would explain the cast. I didn't consider that. Even casting to u16 should work just as well, and I would hope the optimizations don't need that. (Tested on godbolt: Some insignificant differences on 32-bit.)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested on godbolt: Some insignificant differences on 32-bit.

Do you mean 32-bit codegen didn't regress? If so then I suppose it's fine to keep. Otherwise, .ex() can be used

/// Returns the exponent, not adjusting for bias, not accounting for subnormals or zero.
fn ex(self) -> u32 {
u32::cast_from(self.to_bits() >> Self::SIG_BITS) & Self::EXP_SAT
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rewriting in terms of .ex() regressed icounts by ~7% because the semantically clear x.ex() < FOO && x.is_sign_positive() didn't optimize that well. The current version just does unsigned comparisons on ux = x.to_bits(), and seems to be good enough.

@quaternic
Copy link
Contributor Author

To clarify; musl fixed their acoshf but not acosh? And this PR fixes both for us?

Yes, exactly.

Comment on lines 21 to 23
} else if u < 0x3ff + 26 {
/* 2 <= x < 0x1p26 */
log(2.0 * x - 1.0 / (x + sqrt(x * x - 1.0)))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

u is usually the value of x.to_bits() so I'd slightly prefer to just use e here if that works, or give it a different name if not.

@quaternic quaternic force-pushed the acosh-bug branch 2 times, most recently from 33278b2 to e78a7e6 Compare February 7, 2026 00:18
@rustbot
Copy link
Collaborator

rustbot commented Feb 7, 2026

This PR was rebased onto a different main commit. Here's a range-diff highlighting what actually changed.

Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers.

This was left over from f6a23a7 ("fmaximum,fminimum: Fix incorrect
result and add tests").

[ added context to body - Trevor ]
The acosh functions were incorrectly returning finite values for some
negative inputs (should be NaN for any `x < 1.0`)

The bug was inherited when originally ported from musl, and this patch
follows their fix for single-precision acoshf in [1].

A similar fix is applied to acosh, though musl still has an incorrect
implementation requiring tests against that basis to be skipped.

[1]: https://git.musl-libc.org/cgit/musl/commit/?id=c4c38e6364323b6d83ba3428464e19987b981d7a

[ added context to message - Trevor ]
@tgross35 tgross35 enabled auto-merge (rebase) February 7, 2026 06:14
Copy link
Contributor

@tgross35 tgross35 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the updates, LGTM.

I squashed the last two commits since they're related and added some more context to the commit messages.

@tgross35 tgross35 merged commit e04b656 into rust-lang:main Feb 7, 2026
38 checks passed
@quaternic
Copy link
Contributor Author

I squashed the last two commits since they're related and added some more context to the commit messages.

Thanks!

@quaternic quaternic deleted the acosh-bug branch February 7, 2026 08:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants