diff --git a/src/lib.rs b/src/lib.rs index 15a79a8..9a76cca 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,7 @@ use core::{ cmp, fmt, intrinsics::{fadd_fast, fdiv_fast, fmul_fast, frem_fast, fsub_fast}, iter::{Product, Sum}, + num::FpCategory, ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign}, }; @@ -265,6 +266,259 @@ macro_rules! impl_fmt { } } +#[cfg(feature = "num-traits")] +macro_rules! impl_num_traits { + ($fast_ty:ident, $base_ty:ident) => { + impl num_traits::One for $fast_ty { + #[inline(always)] + fn one() -> Self { + Self::ONE + } + + #[inline] + fn is_one(&self) -> bool { + self.freeze_raw() == 1.0 + } + } + + impl num_traits::Zero for $fast_ty { + #[inline(always)] + fn zero() -> Self { + Self::ZERO + } + + #[inline] + fn is_zero(&self) -> bool { + self.freeze_raw() == 0.0 + } + } + + impl num_traits::Num for $fast_ty { + type FromStrRadixErr = <$base_ty as num_traits::Num>::FromStrRadixErr; + + fn from_str_radix(str: &str, radix: u32) -> Result { + Ok(<$fast_ty>::new( + <$base_ty as num_traits::Num>::from_str_radix(str, radix)?, + )) + } + } + + impl num_traits::ToPrimitive for $fast_ty { + forward_freeze_ty! { + $fast_ty, $base_ty + fn to_isize(&self) -> Option ; + fn to_i8(&self) -> Option ; + fn to_i16(&self) -> Option ; + fn to_i32(&self) -> Option ; + fn to_i64(&self) -> Option ; + fn to_i128(&self) -> Option ; + + fn to_usize(&self) -> Option ; + fn to_u8(&self) -> Option ; + fn to_u16(&self) -> Option ; + fn to_u32(&self) -> Option ; + fn to_u64(&self) -> Option ; + fn to_u128(&self) -> Option ; + + fn to_f32(&self) -> Option ; + fn to_f64(&self) -> Option ; + } + } + + impl num_traits::NumCast for $fast_ty { + #[inline] + fn from(n: N) -> Option { + Some(<$fast_ty>::new(<$base_ty as num_traits::NumCast>::from(n)?)) + } + } + + /// Because inf and nan are prohibited, the `fast_fp` types correspond more to the `Real` + /// trait than the `Float` trait. However in practice some libs require a Float bound when + /// they could really use a Real, which would restrict using the `fast_fp` types. + impl num_traits::Float for $fast_ty { + /// Panics because NaN values are not supported + #[inline] + fn nan() -> Self { + panic!(concat!( + stringify!($fast_ty), + " does not support NaN values" + )); + } + + /// Panics because infinite values are not supported + /// + /// Consider using [`max_value`](num_traits::Float::max_value) as appropriate instead + #[inline] + fn infinity() -> Self { + panic!(concat!( + stringify!($fast_ty), + " does not support infinite values. Consider using `max_value` for comparisons" + )); + } + + /// Panics because infinite values are not supported + /// + /// Consider using [`min_value`](num_traits::Float::min_value) as appropriate instead + #[inline] + fn neg_infinity() -> Self { + panic!(concat!( + stringify!($fast_ty), + " does not support infinite values. Consider using `min_value` for comparisons" + )); + } + + #[inline] + fn neg_zero() -> Self { + -Self::ZERO + } + + #[inline] + fn min_value() -> Self { + $fast_ty::MIN + } + + #[inline] + fn min_positive_value() -> Self { + $fast_ty::MIN_POSITIVE + } + + #[inline] + fn max_value() -> Self { + $fast_ty::MAX + } + + #[inline] + fn epsilon() -> Self { + <$fast_ty>::new($base_ty::EPSILON) + } + + #[inline] + fn is_nan(self) -> bool { + false + } + + #[inline] + fn is_infinite(self) -> bool { + false + } + + #[inline] + fn is_finite(self) -> bool { + true + } + + forward_self! { + $fast_ty, $base_ty + fn is_normal(self) -> bool; + fn classify(self) -> FpCategory; + fn floor(self) -> Self; + fn ceil(self) -> Self; + fn round(self) -> Self; + fn trunc(self) -> Self; + fn fract(self) -> Self; + fn abs(self) -> Self; + fn signum(self) -> Self; + fn is_sign_positive(self) -> bool; + fn is_sign_negative(self) -> bool; + fn mul_add(self, a: Self, b: Self) -> Self; + fn recip(self) -> Self; + fn powi(self, n: i32) -> Self; + fn powf(self, n: Self) -> Self; + fn sqrt(self) -> Self; + fn exp(self) -> Self; + fn exp2(self) -> Self; + fn ln(self) -> Self; + fn log(self, base: Self) -> Self; + fn log2(self) -> Self; + fn log10(self) -> Self; + fn max(self, other: Self) -> Self; + fn min(self, other: Self) -> Self; + fn cbrt(self) -> Self; + fn hypot(self, other: Self) -> Self; + fn sin(self) -> Self; + fn cos(self) -> Self; + fn tan(self) -> Self; + fn asin(self) -> Self; + fn acos(self) -> Self; + fn atan(self) -> Self; + fn atan2(self, other: Self) -> Self; + fn sin_cos(self) -> (Self, Self); + fn exp_m1(self) -> Self; + fn ln_1p(self) -> Self; + fn sinh(self) -> Self; + fn cosh(self) -> Self; + fn tanh(self) -> Self; + fn asinh(self) -> Self; + fn acosh(self) -> Self; + fn atanh(self) -> Self; + fn to_degrees(self) -> Self; + fn to_radians(self) -> Self; + } + + forward_freeze_self! { + $fast_ty, $base_ty + #[allow(deprecated)] + fn abs_sub(self, other: Self) -> Self; + } + + #[inline] + fn integer_decode(self) -> (u64, i16, i8) { + <$base_ty as num_traits::Float>::integer_decode(self.freeze_raw()) + } + } + }; +} + +macro_rules! forward_freeze_self { + ($fast_ty:ident, $base_ty:ident + $( + $(#[$attr:meta])* + $vis:vis fn $fn_name:ident (self $(, $arg:ident : Self)* ) -> Self ; + )*) => { + $( + $(#[$attr])* + #[inline] + $vis fn $fn_name(self $(, $arg : Self)*) -> Self { + <$fast_ty>::new(<$base_ty>::$fn_name(self.freeze_raw() $(, $arg.freeze_raw())* )) + } + )* + }; +} + +#[cfg(feature = "num-traits")] +macro_rules! forward_freeze_ty { + ($fast_ty:ident, $base_ty:ident + $( + $(#[$attr:meta])* + $vis:vis fn $fn_name:ident (&self) -> $ret_ty:ty ; + )*) => { + $( + $(#[$attr])* + #[inline] + $vis fn $fn_name(&self) -> $ret_ty { + <$base_ty>::$fn_name(&self.freeze_raw()) + } + )* + } +} + +#[cfg(feature = "num-traits")] +macro_rules! forward_self { + ($fast_ty:ident, $base_ty:ident + $( + $(#[$attr:meta])* + $vis:vis fn $fn_name:ident (self $(, $arg:ident : $arg_ty:ty)* ) -> $ret_ty:ty ; + )*) => { + $( + $(#[$attr])* + #[inline] + $vis fn $fn_name(self $(, $arg : $arg_ty)*) -> $ret_ty { + <$fast_ty>::$fn_name(self $(, $arg)* ) + } + )* + }; +} + macro_rules! impls { ($fast_ty:ident, $base_ty: ident) => { impl $fast_ty { @@ -308,6 +562,106 @@ macro_rules! impls { // every bit pattern is valid in float unsafe { inner.assume_init() } } + + // TODO migrate these to native implementations to freeze less and fast-math more + forward_freeze_self! { + $fast_ty, $base_ty + pub fn abs(self) -> Self; + pub fn acos(self) -> Self; + pub fn acosh(self) -> Self; + pub fn asin(self) -> Self; + pub fn asinh(self) -> Self; + pub fn atan(self) -> Self; + pub fn atan2(self, other: Self) -> Self; + pub fn atanh(self) -> Self; + pub fn cbrt(self) -> Self; + pub fn ceil(self) -> Self; + pub fn clamp(self, min: Self, max: Self) -> Self; + pub fn copysign(self, sign: Self) -> Self; + pub fn cos(self) -> Self; + pub fn cosh(self) -> Self; + pub fn div_euclid(self, rhs: Self) -> Self; + pub fn exp(self) -> Self; + pub fn exp2(self) -> Self; + pub fn exp_m1(self) -> Self; + pub fn floor(self) -> Self; + pub fn fract(self) -> Self; + pub fn hypot(self, other: Self) -> Self; + pub fn ln(self) -> Self; + pub fn ln_1p(self) -> Self; + pub fn log(self, base: Self) -> Self; + pub fn log10(self) -> Self; + pub fn log2(self) -> Self; + pub fn max(self, other: Self) -> Self; + pub fn min(self, other: Self) -> Self; + pub fn mul_add(self, a: Self, b: Self) -> Self; + pub fn powf(self, n: Self) -> Self; + pub fn recip(self) -> Self; + pub fn rem_euclid(self, rhs: Self) -> Self; + pub fn round(self) -> Self; + pub fn signum(self) -> Self; + pub fn sin(self) -> Self; + pub fn sinh(self) -> Self; + pub fn sqrt(self) -> Self; + pub fn tan(self) -> Self; + pub fn tanh(self) -> Self; + pub fn to_degrees(self) -> Self; + pub fn to_radians(self) -> Self; + pub fn trunc(self) -> Self; + } + + #[inline] + pub fn powi(self, n: i32) -> Self { + <$fast_ty>::new(self.freeze_raw().powi(n)) + } + + #[inline] + pub fn sin_cos(self) -> (Self, Self) { + let (sin, cos) = self.freeze_raw().sin_cos(); + (<$fast_ty>::new(sin), <$fast_ty>::new(cos)) + } + + #[inline] + pub fn classify(self) -> FpCategory { + // NaN and infinity should not be presented as possibilities to users, even if + // freeze ends up producing it. Results are unspecified, so Normal is just as valid + // as any other answer + match self.freeze_raw().classify() { + FpCategory::Nan | FpCategory::Infinite => FpCategory::Normal, + category => category + } + } + + #[inline] + pub fn is_sign_negative(self) -> bool { + // must freeze to keep poison out of bool branching + self.freeze_raw().is_sign_negative() + } + + #[inline] + pub fn is_sign_positive(self) -> bool { + // must freeze to keep poison out of bool branching + self.freeze_raw().is_sign_positive() + } + + #[inline] + pub fn is_normal(self) -> bool { + self.classify() == FpCategory::Normal + } + + #[inline] + pub fn is_subnormal(self) -> bool { + self.classify() == FpCategory::Subnormal + } + + /// The smallest finite value + pub const MIN: $fast_ty = <$fast_ty>::new($base_ty::MIN); + + /// The smallest positive value + pub const MIN_POSITIVE: $fast_ty = <$fast_ty>::new($base_ty::MIN_POSITIVE); + + /// The largest finite value + pub const MAX: $fast_ty = <$fast_ty>::new($base_ty::MAX); } impl_fmt! { @@ -454,15 +808,19 @@ macro_rules! impls { } } + #[inline] + fn min(self, other: $fast_ty) -> $fast_ty { + <$fast_ty>::min(self, other) + } + + #[inline] + fn max(self, other: $fast_ty) -> $fast_ty { + <$fast_ty>::max(self, other) + } + #[inline] fn clamp(self, min: $fast_ty, max: $fast_ty) -> $fast_ty { - // TODO implement in terms of min/max, - // TODO also implement min/max (intrinsics? we don't want branches) - <$fast_ty>::new($base_ty::clamp( - self.freeze_raw(), - min.freeze_raw(), - max.freeze_raw(), - )) + <$fast_ty>::clamp(self, min, max) } } @@ -481,6 +839,9 @@ macro_rules! impls { <$fast_ty>::new(from) } } + + #[cfg(feature = "num-traits")] + impl_num_traits! { $fast_ty, $base_ty } }; }