Skip to content

Commit afb2b28

Browse files
authored
Merge pull request #40 from mstoeckl/norm-invg-edge-case
Avoid panic on large alpha for NormalInverseGaussian
2 parents fe2a875 + f7e3c6d commit afb2b28

File tree

2 files changed

+20
-6
lines changed

2 files changed

+20
-6
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1919
### Fixes
2020
- Fix `Geometric::new` for small `p > 0` where `1 - p` rounds to 1 (#36)
2121
- Fix panic in `FisherF::new` on almost zero parameters (#39)
22+
- Fix panic in `NormalInverseGaussian::new` with very large `alpha`; this is a Value-breaking change (#40)
2223

2324
## [0.5.2]
2425

src/normal_inverse_gaussian.rs

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::{Distribution, InverseGaussian, StandardNormal, StandardUniform};
1+
use crate::{Distribution, InverseGaussian, InverseGaussianError, StandardNormal, StandardUniform};
22
use core::fmt;
33
use num_traits::Float;
44
use rand::Rng;
@@ -8,6 +8,8 @@ use rand::Rng;
88
pub enum Error {
99
/// `alpha <= 0` or `nan`.
1010
AlphaNegativeOrNull,
11+
/// `alpha` is `inf` (or, if subnormals are disabled, too close to the maximum finite float value)
12+
AlphaInfinite,
1113
/// `|beta| >= alpha` or `nan`.
1214
AbsoluteBetaNotLessThanAlpha,
1315
}
@@ -18,6 +20,9 @@ impl fmt::Display for Error {
1820
Error::AlphaNegativeOrNull => {
1921
"alpha <= 0 or is NaN in normal inverse Gaussian distribution"
2022
}
23+
Error::AlphaInfinite => {
24+
"alpha is +infinity (or too close to the maximum finite value, if subnormal numbers are not supported) in normal inverse Gaussian distribution"
25+
}
2126
Error::AbsoluteBetaNotLessThanAlpha => {
2227
"|beta| >= alpha or is NaN in normal inverse Gaussian distribution"
2328
}
@@ -68,6 +73,9 @@ where
6873
{
6974
/// Construct a new `NormalInverseGaussian` distribution with the given alpha (tail heaviness) and
7075
/// beta (asymmetry) parameters.
76+
///
77+
/// Note: If subnormal numbers are not supported or are disabled, this sampler may panic or produce
78+
/// incorrect output if alpha is close to the maximum finite float value.
7179
pub fn new(alpha: F, beta: F) -> Result<NormalInverseGaussian<F>, Error> {
7280
if !(alpha > F::zero()) {
7381
return Err(Error::AlphaNegativeOrNull);
@@ -76,12 +84,16 @@ where
7684
if !(beta.abs() < alpha) {
7785
return Err(Error::AbsoluteBetaNotLessThanAlpha);
7886
}
79-
80-
let gamma = (alpha * alpha - beta * beta).sqrt();
81-
87+
// Note: this calculation method for gamma = sqrt(alpha * alpha - beta * beta)
88+
// avoids overflow if alpha is large, ensuring gamma <= alpha, which implies
89+
// (assuming IEEE754 with subnormals) mu = 1.0 / gamma >= 1 / F::max_value() > 0.
90+
let r = beta / alpha;
91+
let gamma = alpha * (F::one() - r * r).sqrt();
8292
let mu = F::one() / gamma;
83-
84-
let inverse_gaussian = InverseGaussian::new(mu, F::one()).unwrap();
93+
let inverse_gaussian = InverseGaussian::new(mu, F::one()).map_err(|x| match x {
94+
InverseGaussianError::MeanNegativeOrNull => Error::AlphaInfinite,
95+
InverseGaussianError::ShapeNegativeOrNull => unreachable!(),
96+
})?;
8597

8698
Ok(Self {
8799
beta,
@@ -124,6 +136,7 @@ mod tests {
124136
assert!(NormalInverseGaussian::new(-1.0, 1.0).is_err());
125137
assert!(NormalInverseGaussian::new(-1.0, -1.0).is_err());
126138
assert!(NormalInverseGaussian::new(1.0, 2.0).is_err());
139+
assert!(NormalInverseGaussian::new(f64::INFINITY, 2.0).is_err());
127140
assert!(NormalInverseGaussian::new(2.0, 1.0).is_ok());
128141
}
129142

0 commit comments

Comments
 (0)