macro_rules! check_finite {
($val:expr) => {
if !$val.is_finite() {
return Err(if $val.is_nan() {
crate::DataError::NotANumber
} else {
crate::DataError::Infinite
});
}
};
}
#[doc(hidden)]
#[inline]
pub fn check_finite(val: f64) -> Result<(), crate::DataError> {
if !val.is_finite() {
return Err(if val.is_nan() {
crate::DataError::NotANumber
} else {
crate::DataError::Infinite
});
}
Ok(())
}
#[doc(hidden)]
#[inline]
pub fn check_finite_f32(val: f32) -> Result<(), crate::DataError> {
if !val.is_finite() {
return Err(if val.is_nan() {
crate::DataError::NotANumber
} else {
crate::DataError::Infinite
});
}
Ok(())
}
#[doc(hidden)]
#[cfg(any(feature = "std", feature = "libm"))]
#[inline]
pub fn sqrt(x: f64) -> f64 {
#[cfg(feature = "std")]
{
x.sqrt()
}
#[cfg(all(not(feature = "std"), feature = "libm"))]
{
libm::sqrt(x)
}
}
#[doc(hidden)]
#[cfg(any(feature = "std", feature = "libm"))]
#[inline]
pub fn exp(x: f64) -> f64 {
#[cfg(feature = "std")]
{
x.exp()
}
#[cfg(all(not(feature = "std"), feature = "libm"))]
{
libm::exp(x)
}
}
#[doc(hidden)]
#[cfg(any(feature = "std", feature = "libm"))]
#[inline]
pub fn ln(x: f64) -> f64 {
#[cfg(feature = "std")]
{
x.ln()
}
#[cfg(all(not(feature = "std"), feature = "libm"))]
{
libm::log(x)
}
}
#[doc(hidden)]
#[cfg(any(feature = "std", feature = "libm"))]
#[inline]
pub fn ln_f32(x: f32) -> f32 {
#[cfg(feature = "std")]
{
x.ln()
}
#[cfg(all(not(feature = "std"), feature = "libm"))]
{
libm::logf(x)
}
}
#[doc(hidden)]
#[cfg(any(feature = "std", feature = "libm"))]
#[inline]
pub fn exp_f32(x: f32) -> f32 {
#[cfg(feature = "std")]
{
x.exp()
}
#[cfg(all(not(feature = "std"), feature = "libm"))]
{
libm::expf(x)
}
}
#[doc(hidden)]
#[cfg(any(feature = "std", feature = "libm"))]
#[allow(
clippy::excessive_precision,
clippy::unreadable_literal,
clippy::suboptimal_flops
)]
pub fn ln_gamma(x: f64) -> f64 {
const G: f64 = 7.5;
const C: [f64; 9] = [
0.99999999999980993,
676.5203681218851,
-1259.1392167224028,
771.32342877765313,
-176.61502916214059,
12.507343278686905,
-0.13857109526572012,
9.9843695780195716e-6,
1.5056327351493116e-7,
];
let x = x - 1.0;
let mut sum = C[0];
for i in 1..9 {
sum += C[i] / (x + i as f64);
}
let t = x + G;
0.5 * ln(2.0 * core::f64::consts::PI) + (x + 0.5) * ln(t) - t + ln(sum)
}
#[doc(hidden)]
pub trait MulAdd {
fn fma(self, b: Self, c: Self) -> Self;
}
impl MulAdd for f64 {
#[inline]
fn fma(self, b: f64, c: f64) -> f64 {
#[cfg(feature = "std")]
{
self.mul_add(b, c)
}
#[cfg(all(not(feature = "std"), feature = "libm"))]
{
libm::fma(self, b, c)
}
#[cfg(not(any(feature = "std", feature = "libm")))]
{
self * b + c
}
}
}
impl MulAdd for f32 {
#[inline]
fn fma(self, b: f32, c: f32) -> f32 {
#[cfg(feature = "std")]
{
self.mul_add(b, c)
}
#[cfg(all(not(feature = "std"), feature = "libm"))]
{
libm::fmaf(self, b, c)
}
#[cfg(not(any(feature = "std", feature = "libm")))]
{
self * b + c
}
}
}
#[cfg(test)]
mod tests {
#[cfg(any(feature = "std", feature = "libm"))]
use super::*;
#[test]
#[cfg(any(feature = "std", feature = "libm"))]
fn ln_gamma_factorial_values() {
assert!((ln_gamma(1.0) - 0.0).abs() < 1e-12);
assert!((ln_gamma(5.0) - ln(24.0)).abs() < 1e-12);
assert!(
0.5f64
.mul_add(-ln(core::f64::consts::PI), ln_gamma(0.5))
.abs()
< 1e-12
);
}
#[test]
#[cfg(any(feature = "std", feature = "libm"))]
fn ln_f32_matches_f64() {
for &x in &[0.5f32, 1.0, 2.0, 10.0, 100.0] {
let f32_result = ln_f32(x);
let f64_result = ln(x as f64) as f32;
assert!(
(f32_result - f64_result).abs() < 1e-6,
"ln_f32({x}) = {f32_result}, expected ≈ {f64_result}"
);
}
}
#[test]
#[cfg(any(feature = "std", feature = "libm"))]
fn exp_f32_matches_f64() {
for &x in &[-2.0f32, -1.0, 0.0, 1.0, 2.0] {
let f32_result = exp_f32(x);
let f64_result = exp(x as f64) as f32;
assert!(
(f32_result - f64_result).abs() < 1e-5,
"exp_f32({x}) = {f32_result}, expected ≈ {f64_result}"
);
}
}
}