use core::cmp;
use core::ops::{Div, Neg, Rem, Sub};
use core::{f32, f64};
use num_traits::{Float, One, PrimInt, Signed, Zero};
pub trait Primitive: Send + Copy + Sized + Clone + PartialOrd + PartialEq {}
impl Primitive for bool {}
pub trait BaseNum:
Primitive + Zero + One + Div<Self, Output = Self> + Rem<Self, Output = Self>
{
fn min(self, other: Self) -> Self;
fn max(self, other: Self) -> Self;
}
pub trait SignedNum: Sized + Neg<Output = Self> + Sub<Self, Output = Self> {
fn abs(&self) -> Self;
fn sign(&self) -> Self;
}
pub trait BaseInt: PrimInt + BaseNum {}
pub trait ApproxEq {
type BaseType: BaseFloat;
fn is_close_to(&self, rhs: &Self, max_diff: Self::BaseType) -> bool;
fn is_approx_eq(&self, rhs: &Self) -> bool {
self.is_close_to(rhs, Self::BaseType::epsilon())
}
}
#[inline(always)]
pub fn is_close_to<T: ApproxEq>(x: &T, y: &T, max_diff: T::BaseType) -> bool {
x.is_close_to(y, max_diff)
}
#[inline(always)]
pub fn is_approx_eq<T: ApproxEq>(x: &T, y: &T) -> bool {
x.is_approx_eq(y)
}
#[macro_export]
macro_rules! assert_approx_eq(
($left: expr, $right: expr) => ({
let lhs = &($left);
let rhs = &($right);
if !is_approx_eq(lhs, rhs) {
panic!(
"assertion failed: left ≈ right` (left: `{:?}`, right: `{:?}`)",
*lhs, *rhs,
)
}
})
);
#[macro_export]
macro_rules! assert_close_to(
($left: expr, $right: expr, $max_diff: expr) => ({
let lhs = &($left);
let rhs = &($right);
let diff = $max_diff;
if !is_close_to(lhs, rhs, diff) {
panic!(
"assertion failed: left ≈ right` (left: `{:?}`, right: `{:?}`, tolerance: `{:?}`)",
*lhs, *rhs, diff
)
}
})
);
pub trait BaseFloat: Float + BaseNum + SignedNum + ApproxEq<BaseType = Self> {
fn to_degrees(self) -> Self;
fn to_radians(self) -> Self;
fn frexp(self) -> (Self, isize);
fn ldexp(self, exp: isize) -> Self;
}
impl SignedNum for i32 {
#[inline(always)]
fn abs(&self) -> i32 {
Signed::abs(self)
}
#[inline(always)]
fn sign(&self) -> i32 {
self.signum()
}
}
macro_rules! impl_int(
($($t: ty), +) => {
$(
impl Primitive for $t {}
impl BaseNum for $t {
#[inline(always)]
fn min(self, other: $t) -> $t {
cmp::min(self, other)
}
#[inline(always)]
fn max(self, other: $t) -> $t {
cmp::max(self, other)
}
}
impl BaseInt for $t {}
)+
}
);
impl_int! { i32, u32 }
macro_rules! impl_flt(
($t: ident) => {
impl Primitive for $t {}
impl SignedNum for $t {
#[inline(always)]
fn abs(&self) -> $t {
Float::abs(*self)
}
#[inline(always)]
fn sign(&self) -> $t {
let l = $t::zero();
if self.is_zero() {
l
} else {
self.signum()
}
}
}
impl ApproxEq for $t {
type BaseType = $t;
#[inline(always)]
fn is_close_to(&self, rhs: &$t, max_diff: $t) -> bool {
(self - *rhs).abs() <= max_diff
}
}
impl BaseNum for $t {
#[inline(always)]
fn min(self, other: $t) -> $t {
self.min(other)
}
#[inline(always)]
fn max(self, other: $t) -> $t {
self.max(other)
}
}
impl BaseFloat for $t {
#[inline(always)]
fn to_degrees(self) -> $t {
self * (180. / $t::consts::PI)
}
#[inline(always)]
fn to_radians(self) -> $t {
self * ($t::consts::PI / 180.)
}
#[inline(always)]
fn frexp(self) -> ($t, isize) {
if self.is_zero() || self.is_infinite() || self.is_nan() {
(self, 0)
} else {
let lg = self.abs().log2();
let x = (lg.fract() - 1.).exp2();
let exp = lg.floor() + 1.;
(self.signum() * x, exp as isize)
}
}
#[inline(always)]
fn ldexp(self, exp: isize) -> $t {
let f = exp as $t;
self * f.exp2()
}
}
}
);
impl_flt! { f32 }
impl_flt! { f64 }