diff options
| author | Ralf Jung <post@ralfj.de> | 2020-06-19 08:55:59 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-06-19 08:55:59 +0200 |
| commit | 99be102a6d18ec05178a0f55ece6c65f65e38c48 (patch) | |
| tree | b7cd435ac7928cae51b04cf00b88c77d01a8f767 | |
| parent | 9c54c65c9d0b3e99f75dbacfaef52953a9a4d0a7 (diff) | |
| parent | 35a2915bf3f24edbbee2d6a9c8f2318304b99df3 (diff) | |
| download | rust-99be102a6d18ec05178a0f55ece6c65f65e38c48.tar.gz rust-99be102a6d18ec05178a0f55ece6c65f65e38c48.zip | |
Rollup merge of #72486 - Ralith:asinh-fix, r=dtolnay
Fix asinh of negative values Rust's current implementation of asinh has [large errors](https://www.wolframalpha.com/input/?i=arcsinh%28x%29%2C+ln%28x%2B%28x%5E2%2B1%29%5E0.5%29%2C+x+from+-67452095.07139316+to+0) in its negative range. ~These are (mostly) not numerical, but rather seem due to an incorrect implementation.~ This appears to be due to avoidable catastrophic cancellation. [Playground before/after](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=bd04ae6d86d06612e4e389a8b95d19ab). [glibc uses](https://github.com/bminor/glibc/blob/81dca813cc35f91414731fdd0ff6b756d5e1827f/sysdeps/ieee754/dbl-64/s_asinh.c#L56) abs here. Many thanks to @danieldeankon for finding this weird behavior, @jebrosen for diagnosing it, and @toasteater for identifying the probable implementation error!
| -rw-r--r-- | src/libstd/f32.rs | 8 | ||||
| -rw-r--r-- | src/libstd/f64.rs | 8 |
2 files changed, 6 insertions, 10 deletions
diff --git a/src/libstd/f32.rs b/src/libstd/f32.rs index d752ba89a27..b392d6e7226 100644 --- a/src/libstd/f32.rs +++ b/src/libstd/f32.rs @@ -832,11 +832,7 @@ impl f32 { #[stable(feature = "rust1", since = "1.0.0")] #[inline] pub fn asinh(self) -> f32 { - if self == Self::NEG_INFINITY { - Self::NEG_INFINITY - } else { - (self + ((self * self) + 1.0).sqrt()).ln().copysign(self) - } + (self.abs() + ((self * self) + 1.0).sqrt()).ln().copysign(self) } /// Inverse hyperbolic cosine function. @@ -1413,6 +1409,8 @@ mod tests { assert!((-0.0f32).asinh().is_sign_negative()); // issue 63271 assert_approx_eq!(2.0f32.asinh(), 1.443635475178810342493276740273105f32); assert_approx_eq!((-2.0f32).asinh(), -1.443635475178810342493276740273105f32); + // regression test for the catastrophic cancellation fixed in 72486 + assert_approx_eq!((-3000.0f32).asinh(), -8.699514775987968673236893537700647f32); } #[test] diff --git a/src/libstd/f64.rs b/src/libstd/f64.rs index 9cd60d846a7..72268d2cc2f 100644 --- a/src/libstd/f64.rs +++ b/src/libstd/f64.rs @@ -834,11 +834,7 @@ impl f64 { #[stable(feature = "rust1", since = "1.0.0")] #[inline] pub fn asinh(self) -> f64 { - if self == Self::NEG_INFINITY { - Self::NEG_INFINITY - } else { - (self + ((self * self) + 1.0).sqrt()).ln().copysign(self) - } + (self.abs() + ((self * self) + 1.0).sqrt()).ln().copysign(self) } /// Inverse hyperbolic cosine function. @@ -1442,6 +1438,8 @@ mod tests { // issue 63271 assert_approx_eq!(2.0f64.asinh(), 1.443635475178810342493276740273105f64); assert_approx_eq!((-2.0f64).asinh(), -1.443635475178810342493276740273105f64); + // regression test for the catastrophic cancellation fixed in 72486 + assert_approx_eq!((-67452098.07139316f64).asinh(), -18.72007542627454439398548429400083); } #[test] |
