Skip to content

Commit

Permalink
Fix asinh of negative values
Browse files Browse the repository at this point in the history
When `x` has large magnitude, `x + ((x * x) + 1.0).sqrt()` approaches
`x + x.abs()`. For negative values of `x`, this leads to catastrophic
cancellation, resulting in large errors or even 0 being passed to
`ln`, producing incorrect results including `-inf`.

Becuase asinh is an odd function, i.e. -asinh(x) = asinh(-x) for all
x, we can avoid the catastrophic cancellation and obtain correct
results by taking the absolute value of `self` for the first
term. `self * self` is always positive, so in effect this gives us
`x.abs().asinh().copysign(x)` which as discussed above is
algebraically equivalent, but is much more accurate.
  • Loading branch information
Ralith committed May 24, 2020
1 parent 215f2d3 commit 730f736
Show file tree
Hide file tree
Showing 2 changed files with 6 additions and 2 deletions.
4 changes: 3 additions & 1 deletion src/libstd/f32.rs
Expand Up @@ -835,7 +835,7 @@ impl 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)
}
}

Expand Down Expand Up @@ -1414,6 +1414,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]
Expand Down
4 changes: 3 additions & 1 deletion src/libstd/f64.rs
Expand Up @@ -837,7 +837,7 @@ impl 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)
}
}

Expand Down Expand Up @@ -1443,6 +1443,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]
Expand Down

0 comments on commit 730f736

Please sign in to comment.