use core::fmt::Debug;
use core::iter::Sum;
use core::ops::{Add, AddAssign, Div, Mul, MulAssign, Neg, Sub, SubAssign};
pub trait Real:
Copy
+ Debug
+ PartialOrd
+ Add<Output = Self>
+ Sub<Output = Self>
+ Mul<Output = Self>
+ Div<Output = Self>
+ Neg<Output = Self>
+ AddAssign
+ SubAssign
+ MulAssign
+ Sum
+ 'static
{
const ZERO: Self;
const ONE: Self;
const EPSILON: Self;
fn from_f64(x: f64) -> Self;
fn to_f64(self) -> f64;
#[must_use]
fn abs(self) -> Self;
#[must_use]
fn sqrt(self) -> Self;
#[must_use]
fn recip(self) -> Self;
#[must_use]
fn mul_add(self, a: Self, b: Self) -> Self;
fn is_finite(self) -> bool;
#[must_use]
fn max(self, other: Self) -> Self;
#[must_use]
fn min(self, other: Self) -> Self;
}
impl Real for f64 {
const ZERO: Self = 0.0;
const ONE: Self = 1.0;
const EPSILON: Self = f64::EPSILON;
#[inline]
fn from_f64(x: f64) -> Self {
x
}
#[inline]
fn to_f64(self) -> f64 {
self
}
#[inline]
fn abs(self) -> Self {
f64::abs(self)
}
#[inline]
fn sqrt(self) -> Self {
f64::sqrt(self)
}
#[inline]
fn recip(self) -> Self {
f64::recip(self)
}
#[inline]
fn mul_add(self, a: Self, b: Self) -> Self {
f64::mul_add(self, a, b)
}
#[inline]
fn is_finite(self) -> bool {
f64::is_finite(self)
}
#[inline]
fn max(self, other: Self) -> Self {
f64::max(self, other)
}
#[inline]
fn min(self, other: Self) -> Self {
f64::min(self, other)
}
}
#[inline]
fn scale<T: Real>(a: T, b: T) -> T {
T::ONE.max(a.abs()).max(b.abs())
}
#[inline]
pub(crate) fn lt<T: Real>(a: T, b: T) -> bool {
lt_eps(a, b, T::ZERO)
}
#[inline]
pub(crate) fn lt_eps<T: Real>(a: T, b: T, eps: T) -> bool {
debug_assert!(!is_nan(a) && !is_nan(b), "lt called with NaN");
if a.is_finite() && b.is_finite() {
b - a > eps * scale(a, b)
} else {
a < b
}
}
#[inline]
#[cfg_attr(not(test), allow(dead_code))] pub(crate) fn le<T: Real>(a: T, b: T, eps: T) -> bool {
!lt_eps(b, a, eps)
}
#[inline]
pub(crate) fn le_exact<T: Real>(a: T, b: T) -> bool {
debug_assert!(!is_nan(a) && !is_nan(b), "le_exact called with NaN");
a <= b
}
#[inline]
pub(crate) fn approx_eq<T: Real>(a: T, b: T, eps: T) -> bool {
debug_assert!(!is_nan(a) && !is_nan(b), "approx_eq called with NaN");
if a.is_finite() && b.is_finite() {
(a - b).abs() <= eps * scale(a, b)
} else {
a == b
}
}
#[inline]
pub(crate) fn is_zero<T: Real>(x: T, eps: T) -> bool {
debug_assert!(!is_nan(x), "is_zero called with NaN");
x.abs() <= eps
}
#[inline]
pub(crate) fn is_exact_zero<T: Real>(x: T) -> bool {
x == T::ZERO
}
#[inline]
#[allow(clippy::eq_op)] pub(crate) fn is_nan<T: Real>(x: T) -> bool {
x != x
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn comparisons_match_their_definitions() {
assert!(lt(1.0, 2.0) && !lt(2.0, 2.0) && !lt(3.0, 2.0));
assert!(lt(f64::NEG_INFINITY, -1e308) && !lt(f64::INFINITY, f64::INFINITY));
assert!(le(2.0, 2.0, 0.0) && le(2.0 + 1e-13, 2.0, 1e-12));
assert!(approx_eq(1e6, 1e6 + 1e-7, 1e-12));
assert!(approx_eq(f64::INFINITY, f64::INFINITY, 0.0));
assert!(!approx_eq(1e308, f64::INFINITY, 1e-9));
assert!(is_zero(1e-13, 1e-12) && !is_zero(1e-11, 1e-12));
assert!(is_exact_zero(-0.0) && !is_exact_zero(f64::MIN_POSITIVE));
assert!(is_nan(f64::NAN) && !is_nan(1.0));
}
#[test]
fn f64_is_real() {
fn roundtrip<T: Real>(x: f64) -> f64 {
T::from_f64(x).to_f64()
}
assert!(approx_eq(roundtrip::<f64>(0.1), 0.1, 0.0));
assert!(approx_eq(Real::abs(-2.0_f64), 2.0, 0.0));
assert!(approx_eq(Real::mul_add(2.0_f64, 3.0, 1.0), 7.0, 0.0));
assert!(approx_eq(Real::sqrt(4.0_f64), 2.0, 0.0));
assert!(approx_eq(Real::recip(4.0_f64), 0.25, 0.0));
assert!(approx_eq(Real::min(4.0_f64, 1.0), 1.0, 0.0));
assert!(approx_eq(<f64 as Real>::EPSILON, f64::EPSILON, 0.0));
let s: f64 = [1.0, 2.0, 3.0].into_iter().sum();
assert!(approx_eq(s, 6.0, 0.0));
}
}