use core::cmp::Ordering;
use core::fmt::Debug;
use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
mod sealed {
pub trait Sealed {}
}
#[allow(private_bounds)] pub trait Real:
sealed::Sealed
+ Copy
+ Debug
+ Default
+ PartialEq
+ PartialOrd
+ Add<Output = Self>
+ AddAssign
+ Sub<Output = Self>
+ SubAssign
+ Mul<Output = Self>
+ MulAssign
+ Div<Output = Self>
+ DivAssign
+ Neg<Output = Self>
+ Send
+ Sync
+ 'static
{
const ZERO: Self;
const ONE: Self;
const TWO: Self;
const HALF: Self;
const EPSILON: Self;
const INFINITY: Self;
const NEG_INFINITY: Self;
const DIFF_STEP: Self;
#[must_use]
fn sqrt(self) -> Self;
#[must_use]
fn floor(self) -> Self;
#[must_use]
fn sin(self) -> Self;
#[must_use]
fn cos(self) -> Self;
#[must_use]
fn abs(self) -> Self;
#[must_use]
fn signum(self) -> Self;
#[must_use]
fn recip(self) -> Self;
#[must_use]
fn min(self, other: Self) -> Self;
#[must_use]
fn max(self, other: Self) -> Self;
#[must_use]
fn clamp(self, low: Self, high: Self) -> Self;
fn is_finite(self) -> bool;
fn total_cmp(&self, other: &Self) -> Ordering;
#[must_use]
fn from_f64(value: f64) -> Self;
#[must_use]
fn as_f32(self) -> f32;
#[must_use]
fn as_f64(self) -> f64;
}
macro_rules! impl_real {
(
$ty:ty,
diff_step = $diff_step:expr,
sqrt = $sqrt:path, floor = $floor:path, sin = $sin:path, cos = $cos:path $(,)?
) => {
impl sealed::Sealed for $ty {}
impl Real for $ty {
const ZERO: Self = 0.0;
const ONE: Self = 1.0;
const TWO: Self = 2.0;
const HALF: Self = 0.5;
const EPSILON: Self = <$ty>::EPSILON;
const INFINITY: Self = <$ty>::INFINITY;
const NEG_INFINITY: Self = <$ty>::NEG_INFINITY;
const DIFF_STEP: Self = $diff_step;
#[inline]
fn sqrt(self) -> Self {
$sqrt(self)
}
#[inline]
fn floor(self) -> Self {
$floor(self)
}
#[inline]
fn sin(self) -> Self {
$sin(self)
}
#[inline]
fn cos(self) -> Self {
$cos(self)
}
#[inline]
fn abs(self) -> Self {
<$ty>::abs(self)
}
#[inline]
fn signum(self) -> Self {
<$ty>::signum(self)
}
#[inline]
fn recip(self) -> Self {
<$ty>::recip(self)
}
#[inline]
fn min(self, other: Self) -> Self {
<$ty>::min(self, other)
}
#[inline]
fn max(self, other: Self) -> Self {
<$ty>::max(self, other)
}
#[inline]
fn clamp(self, low: Self, high: Self) -> Self {
<$ty>::clamp(self, low, high)
}
#[inline]
fn is_finite(self) -> bool {
<$ty>::is_finite(self)
}
#[inline]
fn total_cmp(&self, other: &Self) -> Ordering {
<$ty>::total_cmp(self, other)
}
#[inline]
#[allow(clippy::unnecessary_cast)] fn from_f64(value: f64) -> Self {
value as $ty
}
#[inline]
#[allow(clippy::unnecessary_cast)] fn as_f32(self) -> f32 {
self as f32
}
#[inline]
#[allow(clippy::unnecessary_cast)] fn as_f64(self) -> f64 {
self as f64
}
}
};
}
impl_real!(
f32,
diff_step = 4.921_567e-3,
sqrt = libm::sqrtf,
floor = libm::floorf,
sin = libm::sinf,
cos = libm::cosf,
);
impl_real!(
f64,
diff_step = 6.055_454_452_393_343e-6,
sqrt = libm::sqrt,
floor = libm::floor,
sin = libm::sin,
cos = libm::cos,
);
#[cfg(test)]
mod tests {
#![allow(clippy::float_cmp)]
use super::*;
#[test]
fn constants_are_exact() {
assert_eq!(f32::ZERO, 0.0);
assert_eq!(f32::ONE, 1.0);
assert_eq!(f32::TWO, 2.0);
assert_eq!(f32::HALF, 0.5);
assert_eq!(<f32 as Real>::EPSILON, f32::EPSILON);
assert!(<f32 as Real>::INFINITY.is_infinite());
assert_eq!(f64::ZERO, 0.0);
assert_eq!(f64::ONE, 1.0);
assert_eq!(f64::TWO, 2.0);
assert_eq!(f64::HALF, 0.5);
assert_eq!(<f64 as Real>::EPSILON, f64::EPSILON);
assert!(<f64 as Real>::NEG_INFINITY.is_infinite());
}
#[test]
fn diff_step_is_the_cube_root_of_epsilon() {
let cube = f64::from(f32::DIFF_STEP).powi(3);
let eps = f64::from(f32::EPSILON);
assert!(
cube > 0.5 * eps && cube < 2.0 * eps,
"f32: {cube:e} vs {eps:e}"
);
let cube = f64::DIFF_STEP.powi(3);
let eps = <f64 as Real>::EPSILON;
assert!(
cube > 0.5 * eps && cube < 2.0 * eps,
"f64: {cube:e} vs {eps:e}"
);
}
#[test]
fn backend_matches_reference_values() {
assert_eq!(Real::sqrt(2.0f32), core::f32::consts::SQRT_2);
assert_eq!(Real::sqrt(2.0f64), core::f64::consts::SQRT_2);
assert_eq!(Real::floor(-1.5f32), -2.0);
assert_eq!(Real::floor(-1.5f64), -2.0);
assert_eq!(Real::abs(-0.0f32), 0.0);
assert!((Real::sin(1.0f32) - 0.841_470_98_f32).abs() <= 4.0 * f32::EPSILON);
assert!((Real::cos(1.0f32) - 0.540_302_3_f32).abs() <= 4.0 * f32::EPSILON);
assert!((Real::sin(1.0f64) - 0.841_470_984_807_896_5).abs() <= 4.0 * f64::EPSILON);
assert!((Real::cos(1.0f64) - 0.540_302_305_868_139_7).abs() <= 4.0 * f64::EPSILON);
}
#[test]
fn from_f64_builds_at_the_targets_precision() {
fn literal<R: Real>() -> R {
R::from_f64(0.3)
}
assert_eq!(literal::<f64>(), 0.3f64);
assert_eq!(literal::<f32>(), 0.3f32);
assert_ne!(literal::<f64>(), f64::from(0.3f32));
assert_eq!(<f64 as Real>::from_f64(0.75), 0.75f64);
assert_eq!(<f32 as Real>::from_f64(0.75), 0.75f32);
}
#[test]
fn as_f32_narrows_and_saturates() {
assert!(Real::as_f32(1e300f64).is_infinite());
assert_eq!(Real::as_f32(1.5f64), 1.5f32);
assert_eq!(Real::as_f32(1.5f32), 1.5f32);
}
#[test]
fn total_cmp_separates_signed_zero() {
assert_eq!(Real::total_cmp(&-0.0f32, &0.0f32), Ordering::Less);
assert_eq!(Real::total_cmp(&-0.0f64, &0.0f64), Ordering::Less);
assert_eq!((-0.0f32).partial_cmp(&0.0f32), Some(Ordering::Equal));
}
}