Преглед изворни кода

Implement CoreFloat trait

This is a subset of the `Float` trait, but works with `no_std`.
Some code was simplified by using `CoreFloat`.
Vinzent Steinberg пре 7 година
родитељ
комит
8a7f383eb1
4 измењених фајлова са 247 додато и 57 уклоњено
  1. 2 2
      src/cast.rs
  2. 240 20
      src/float.rs
  3. 1 1
      src/lib.rs
  4. 4 34
      src/sign.rs

+ 2 - 2
src/cast.rs

@@ -4,6 +4,7 @@ use core::num::Wrapping;
 
 use identities::Zero;
 use bounds::Bounded;
+use float::CoreFloat;
 
 /// 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 !CoreFloat::is_finite(n) || (-max_value as f64 <= n && n <= max_value as f64)
             {
                 Some($slf as $DstT)
             } else {

+ 240 - 20
src/float.rs

@@ -1,16 +1,221 @@
-#[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 CoreFloat: Num + Neg<Output = Self> + PartialOrd + Copy {
+    /// Returns positive infinity.
+    #[inline]
+    fn infinity() -> Self;
+
+    /// Returns negative infinity.
+    #[inline]
+    fn neg_infinity() -> Self;
+
+    /// Returns NaN.
+    #[inline]
+    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.
+    #[inline]
+    fn classify(self) -> FpCategory;
+
+    /// Computes the absolute value of `self`. Returns `CoreFloat::nan()` if the
+    /// number is `CoreFloat::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 `CoreFloat::infinity()`
+    /// - `-1.0` if the number is negative, `-0.0` or `CoreFloat::neg_infinity()`
+    /// - `CoreFloat::nan()` if the number is `CoreFloat::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
+    /// `CoreFloat::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
+    /// `CoreFloat::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.
+    #[inline]
+    fn to_degrees(self) -> Self;
+
+    /// Converts to radians, assuming the number is in degrees.
+    #[inline]
+    fn to_radians(self) -> Self;
+}
+
+impl CoreFloat for f32 {
+    fn infinity() -> Self {
+        ::core::f32::INFINITY
+    }
+    fn neg_infinity() -> Self {
+        ::core::f32::NEG_INFINITY
+    }
+    fn nan() -> Self {
+        ::core::f32::NAN
+    }
+    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,
+        }
+    }
+    fn to_degrees(self) -> Self {
+        self * (180.0 / ::core::f32::consts::PI)
+    }
+    fn to_radians(self) -> Self {
+        self * (::core::f32::consts::PI / 180.0)
+    }
+}
+
+impl CoreFloat for f64 {
+    fn infinity() -> Self {
+        ::core::f64::INFINITY
+    }
+    fn neg_infinity() -> Self {
+        ::core::f64::NEG_INFINITY
+    }
+    fn nan() -> Self {
+        ::core::f64::NAN
+    }
+    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,
+        }
+    }
+    fn to_degrees(self) -> Self {
+        self * (180.0 / ::core::f64::consts::PI)
+    }
+    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 +1533,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 CoreFloat;
 
         for &(deg, rad) in &DEG_RAD_PAIRS {
+            assert!((CoreFloat::to_degrees(rad) - deg).abs() < 1e-6);
+            assert!((CoreFloat::to_radians(deg) - rad).abs() < 1e-6);
+
+            let (deg, rad) = (deg as f32, rad as f32);
+            assert!((CoreFloat::to_degrees(rad) - deg).abs() < 1e-6);
+            assert!((CoreFloat::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);
 

+ 1 - 1
src/lib.rs

@@ -26,7 +26,7 @@ use core::fmt;
 pub use bounds::Bounded;
 #[cfg(feature = "std")]
 pub use float::Float;
-pub use float::FloatConst;
+pub use float::{CoreFloat, FloatConst};
 // pub use real::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};

+ 4 - 34
src/sign.rs

@@ -3,6 +3,8 @@ use core::{f32, f64};
 use core::num::Wrapping;
 
 use Num;
+#[cfg(not(feature = "std"))]
+use float::CoreFloat;
 
 /// Useful functions for signed numbers (i.e. numbers that can be negative).
 pub trait Signed: Sized + Num + Neg<Output = Self> {
@@ -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 CoreFloat;
+                CoreFloat::signum(*self)
             }
 
             /// Returns `true` if the number is positive, including `+0.0` and `INFINITY`