Skip to content

Commit f7e3c6d

Browse files
committed
Avoid panic on large alpha for NormalInverseGaussian
The new calculation method avoids calculating alpha*alpha, which could produce `inf` and lead to invalid parameters for InverseGaussian. Also, an explicit error is now returned when alpha is inf. The new parameter calculation can slightly affect the values sampled by the distribution.
1 parent fe2a875 commit f7e3c6d

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)