diff --git a/README.md b/README.md index 7381dfe..9702795 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,8 @@ version = "0.2" default-features = false ``` -The `Float` and `Real` traits are only available when `std` is enabled. +The `Float` and `Real` traits are only available when `std` is enabled. The +`FloatCore` trait is always available. ## Releases diff --git a/src/cast.rs b/src/cast.rs index 9ac530b..3d7d0a6 100644 --- a/src/cast.rs +++ b/src/cast.rs @@ -4,6 +4,7 @@ use core::num::Wrapping; use identities::Zero; use bounds::Bounded; +use float::FloatCore; /// A generic trait for converting a value to a number. pub trait ToPrimitive { @@ -228,8 +229,7 @@ macro_rules! impl_to_primitive_float_to_float { // NaN and +-inf are cast as they are. let n = $slf as f64; let max_value: $DstT = ::core::$DstT::MAX; - if n != n || n == f64::INFINITY || n == f64::NEG_INFINITY - || (-max_value as f64 <= n && n <= max_value as f64) + if !FloatCore::is_finite(n) || (-max_value as f64 <= n && n <= max_value as f64) { Some($slf as $DstT) } else { diff --git a/src/float.rs b/src/float.rs index 3a7e931..5ca80d2 100644 --- a/src/float.rs +++ b/src/float.rs @@ -1,16 +1,237 @@ -#[cfg(feature = "std")] -use std::mem; -#[cfg(feature = "std")] -use std::ops::Neg; -#[cfg(feature = "std")] -use std::num::FpCategory; +use core::mem; +use core::ops::Neg; +use core::num::FpCategory; // Used for default implementation of `epsilon` #[cfg(feature = "std")] use std::f32; +use {Num, ToPrimitive}; #[cfg(feature = "std")] -use {Num, NumCast}; +use NumCast; + +/// Generic trait for floating point numbers that works with `no_std`. +/// +/// This trait implements a subset of the `Float` trait. +pub trait FloatCore: Num + Neg + PartialOrd + Copy { + /// Returns positive infinity. + fn infinity() -> Self; + + /// Returns negative infinity. + fn neg_infinity() -> Self; + + /// Returns NaN. + fn nan() -> Self; + + /// Returns `true` if the number is NaN. + #[inline] + fn is_nan(self) -> bool { + self != self + } + + /// Returns `true` if the number is infinite. + #[inline] + fn is_infinite(self) -> bool { + self == Self::infinity() || self == Self::neg_infinity() + } + + /// Returns `true` if the number is neither infinite or NaN. + #[inline] + fn is_finite(self) -> bool { + !(self.is_nan() || self.is_infinite()) + } + + /// Returns `true` if the number is neither zero, infinite, subnormal or NaN. + #[inline] + fn is_normal(self) -> bool { + self.classify() == FpCategory::Normal + } + + /// Returns the floating point category of the number. If only one property + /// is going to be tested, it is generally faster to use the specific + /// predicate instead. + fn classify(self) -> FpCategory; + + /// Computes the absolute value of `self`. Returns `FloatCore::nan()` if the + /// number is `FloatCore::nan()`. + #[inline] + fn abs(self) -> Self { + if self.is_sign_positive() { + return self; + } + if self.is_sign_negative() { + return -self; + } + Self::nan() + } + + /// Returns a number that represents the sign of `self`. + /// + /// - `1.0` if the number is positive, `+0.0` or `FloatCore::infinity()` + /// - `-1.0` if the number is negative, `-0.0` or `FloatCore::neg_infinity()` + /// - `FloatCore::nan()` if the number is `FloatCore::nan()` + #[inline] + fn signum(self) -> Self { + if self.is_sign_positive() { + return Self::one(); + } + if self.is_sign_negative() { + return -Self::one(); + } + Self::nan() + } + + /// Returns `true` if `self` is positive, including `+0.0` and + /// `FloatCore::infinity()`. + #[inline] + fn is_sign_positive(self) -> bool { + self > Self::zero() || (Self::one() / self) == Self::infinity() + } + + /// Returns `true` if `self` is negative, including `-0.0` and + /// `FloatCore::neg_infinity()`. + #[inline] + fn is_sign_negative(self) -> bool { + self < Self::zero() || (Self::one() / self) == Self::neg_infinity() + } + + /// Returns the minimum of the two numbers. + /// + /// If one of the arguments is NaN, then the other argument is returned. + #[inline] + fn min(self, other: Self) -> Self { + if self.is_nan() { + return other; + } + if other.is_nan() { + return self; + } + if self < other { self } else { other } + } + + /// Returns the maximum of the two numbers. + /// + /// If one of the arguments is NaN, then the other argument is returned. + #[inline] + fn max(self, other: Self) -> Self { + if self.is_nan() { + return other; + } + if other.is_nan() { + return self; + } + if self > other { self } else { other } + } + + /// Returns the reciprocal (multiplicative inverse) of the number. + #[inline] + fn recip(self) -> Self { + Self::one() / self + } + + /// Raise a number to an integer power. + /// + /// Using this function is generally faster than using `powf` + #[inline] + fn powi(mut self, mut exp: i32) -> Self { + if exp < 0 { + exp = -exp; + self = self.recip(); + } + // It should always be possible to convert a positive `i32` to a `usize`. + super::pow(self, exp.to_usize().unwrap()) + } + + /// Converts to degrees, assuming the number is in radians. + fn to_degrees(self) -> Self; + + /// Converts to radians, assuming the number is in degrees. + fn to_radians(self) -> Self; +} + +impl FloatCore for f32 { + #[inline] + fn infinity() -> Self { + ::core::f32::INFINITY + } + + #[inline] + fn neg_infinity() -> Self { + ::core::f32::NEG_INFINITY + } + + #[inline] + fn nan() -> Self { + ::core::f32::NAN + } + + #[inline] + fn classify(self) -> FpCategory { + const EXP_MASK: u32 = 0x7f800000; + const MAN_MASK: u32 = 0x007fffff; + + let bits: u32 = unsafe { mem::transmute(self) }; + match (bits & MAN_MASK, bits & EXP_MASK) { + (0, 0) => FpCategory::Zero, + (_, 0) => FpCategory::Subnormal, + (0, EXP_MASK) => FpCategory::Infinite, + (_, EXP_MASK) => FpCategory::Nan, + _ => FpCategory::Normal, + } + } + + #[inline] + fn to_degrees(self) -> Self { + self * (180.0 / ::core::f32::consts::PI) + } + + #[inline] + fn to_radians(self) -> Self { + self * (::core::f32::consts::PI / 180.0) + } +} + +impl FloatCore for f64 { + #[inline] + fn infinity() -> Self { + ::core::f64::INFINITY + } + + #[inline] + fn neg_infinity() -> Self { + ::core::f64::NEG_INFINITY + } + + #[inline] + fn nan() -> Self { + ::core::f64::NAN + } + + #[inline] + fn classify(self) -> FpCategory { + const EXP_MASK: u64 = 0x7ff0000000000000; + const MAN_MASK: u64 = 0x000fffffffffffff; + + let bits: u64 = unsafe { mem::transmute(self) }; + match (bits & MAN_MASK, bits & EXP_MASK) { + (0, 0) => FpCategory::Zero, + (_, 0) => FpCategory::Subnormal, + (0, EXP_MASK) => FpCategory::Infinite, + (_, EXP_MASK) => FpCategory::Nan, + _ => FpCategory::Normal, + } + } + + #[inline] + fn to_degrees(self) -> Self { + self * (180.0 / ::core::f64::consts::PI) + } + + #[inline] + fn to_radians(self) -> Self { + self * (::core::f64::consts::PI / 180.0) + } +} // FIXME: these doctests aren't actually helpful, because they're using and // testing the inherent methods directly, not going through `Float`. @@ -1328,25 +1549,40 @@ float_const_impl! { SQRT_2, } -#[cfg(all(test, feature = "std"))] +#[cfg(test)] mod tests { - use Float; + use core::f64::consts; + + const DEG_RAD_PAIRS: [(f64, f64); 7] = [ + (0.0, 0.), + (22.5, consts::FRAC_PI_8), + (30.0, consts::FRAC_PI_6), + (45.0, consts::FRAC_PI_4), + (60.0, consts::FRAC_PI_3), + (90.0, consts::FRAC_PI_2), + (180.0, consts::PI), + ]; #[test] fn convert_deg_rad() { - use core::f64::consts; - - const DEG_RAD_PAIRS: [(f64, f64); 7] = [ - (0.0, 0.), - (22.5, consts::FRAC_PI_8), - (30.0, consts::FRAC_PI_6), - (45.0, consts::FRAC_PI_4), - (60.0, consts::FRAC_PI_3), - (90.0, consts::FRAC_PI_2), - (180.0, consts::PI), - ]; + use float::FloatCore; for &(deg, rad) in &DEG_RAD_PAIRS { + assert!((FloatCore::to_degrees(rad) - deg).abs() < 1e-6); + assert!((FloatCore::to_radians(deg) - rad).abs() < 1e-6); + + let (deg, rad) = (deg as f32, rad as f32); + assert!((FloatCore::to_degrees(rad) - deg).abs() < 1e-6); + assert!((FloatCore::to_radians(deg) - rad).abs() < 1e-6); + } + } + + #[cfg(feature = "std")] + #[test] + fn convert_deg_rad_std() { + for &(deg, rad) in &DEG_RAD_PAIRS { + use Float; + assert!((Float::to_degrees(rad) - deg).abs() < 1e-6); assert!((Float::to_radians(deg) - rad).abs() < 1e-6); diff --git a/src/lib.rs b/src/lib.rs index a388fd5..83a45f9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,7 +31,7 @@ pub use bounds::Bounded; #[cfg(feature = "std")] pub use float::Float; pub use float::FloatConst; -// pub use real::Real; // NOTE: Don't do this, it breaks `use num_traits::*;`. +// pub use real::{FloatCore, Real}; // NOTE: Don't do this, it breaks `use num_traits::*;`. pub use identities::{Zero, One, zero, one}; pub use ops::checked::{CheckedAdd, CheckedSub, CheckedMul, CheckedDiv, CheckedShl, CheckedShr}; pub use ops::wrapping::{WrappingAdd, WrappingMul, WrappingSub}; diff --git a/src/sign.rs b/src/sign.rs index f5769ab..437c8bf 100644 --- a/src/sign.rs +++ b/src/sign.rs @@ -3,6 +3,8 @@ use core::{f32, f64}; use core::num::Wrapping; use Num; +#[cfg(not(feature = "std"))] +use float::FloatCore; /// Useful functions for signed numbers (i.e. numbers that can be negative). pub trait Signed: Sized + Num + Neg { @@ -103,24 +105,10 @@ macro_rules! signed_float_impl { impl Signed for $t { /// Computes the absolute value. Returns `NAN` if the number is `NAN`. #[inline] - #[cfg(feature = "std")] fn abs(&self) -> $t { (*self).abs() } - /// Computes the absolute value. Returns `NAN` if the number is `NAN`. - #[inline] - #[cfg(not(feature = "std"))] - fn abs(&self) -> $t { - if self.is_positive() { - *self - } else if self.is_negative() { - -*self - } else { - $nan - } - } - /// The positive difference of two numbers. Returns `0.0` if the number is /// less than or equal to `other`, otherwise the difference between`self` /// and `other` is returned. @@ -135,27 +123,9 @@ macro_rules! signed_float_impl { /// - `-1.0` if the number is negative, `-0.0` or `NEG_INFINITY` /// - `NAN` if the number is NaN #[inline] - #[cfg(feature = "std")] fn signum(&self) -> $t { - use Float; - Float::signum(*self) - } - - /// # Returns - /// - /// - `1.0` if the number is positive, `+0.0` or `INFINITY` - /// - `-1.0` if the number is negative, `-0.0` or `NEG_INFINITY` - /// - `NAN` if the number is NaN - #[inline] - #[cfg(not(feature = "std"))] - fn signum(&self) -> $t { - if self.is_positive() { - 1.0 - } else if self.is_negative() { - -1.0 - } else { - $nan - } + use float::FloatCore; + FloatCore::signum(*self) } /// Returns `true` if the number is positive, including `+0.0` and `INFINITY`