From cc51186be0c68897042a72bd52147e345ad2a2cd Mon Sep 17 00:00:00 2001 From: Brendan Zabarauskas Date: Tue, 7 May 2013 20:30:51 +1000 Subject: [PATCH] Add is_normal and classify methods to Float trait --- src/libcore/num/f32.rs | 55 ++++++++++++++++++++++++++++++++++++- src/libcore/num/f64.rs | 54 +++++++++++++++++++++++++++++++++++- src/libcore/num/float.rs | 59 ++++++++++++++++++++++++++++++---------- src/libcore/num/num.rs | 19 +++++++++++++ 4 files changed, 170 insertions(+), 17 deletions(-) diff --git a/src/libcore/num/f32.rs b/src/libcore/num/f32.rs index bdd11200419f..3c4faa95dd19 100644 --- a/src/libcore/num/f32.rs +++ b/src/libcore/num/f32.rs @@ -11,6 +11,7 @@ //! Operations and constants for `f32` use num::{Zero, One, strconv}; +use num::{FPCategory, FPNaN, FPInfinite , FPZero, FPSubnormal, FPNormal}; use prelude::*; pub use cmath::c_float_targ_consts::*; @@ -568,12 +569,39 @@ impl Float for f32 { *self == Float::infinity() || *self == Float::neg_infinity() } - /// Returns `true` if the number is not infinite or NaN + /// Returns `true` if the number is neither infinite or NaN #[inline(always)] fn is_finite(&self) -> bool { !(self.is_NaN() || self.is_infinite()) } + /// Returns `true` if the number is neither zero, infinite, subnormal or NaN + #[inline(always)] + fn is_normal(&self) -> bool { + match self.classify() { + FPNormal => true, + _ => false, + } + } + + /// 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 { + static EXP_MASK: u32 = 0x7f800000; + static MAN_MASK: u32 = 0x007fffff; + + match ( + unsafe { ::cast::transmute::(*self) } & EXP_MASK, + unsafe { ::cast::transmute::(*self) } & MAN_MASK + ) { + (EXP_MASK, 0) => FPInfinite, + (EXP_MASK, _) => FPNaN, + (exp, _) if exp != 0 => FPNormal, + _ if self.is_zero() => FPZero, + _ => FPSubnormal, + } + } + #[inline(always)] fn mantissa_digits() -> uint { 24 } @@ -846,6 +874,7 @@ impl num::FromStrRadix for f32 { #[cfg(test)] mod tests { use f32::*; + use num::*; use super::*; use prelude::*; @@ -1041,4 +1070,28 @@ mod tests { assert_eq!(Primitive::bits::(), sys::size_of::() * 8); assert_eq!(Primitive::bytes::(), sys::size_of::()); } + + #[test] + fn test_is_normal() { + assert!(!Float::NaN::().is_normal()); + assert!(!Float::infinity::().is_normal()); + assert!(!Float::neg_infinity::().is_normal()); + assert!(!Zero::zero::().is_normal()); + assert!(!Float::neg_zero::().is_normal()); + assert!(1f32.is_normal()); + assert!(1e-37f32.is_normal()); + assert!(!1e-38f32.is_normal()); + } + + #[test] + fn test_classify() { + assert_eq!(Float::NaN::().classify(), FPNaN); + assert_eq!(Float::infinity::().classify(), FPInfinite); + assert_eq!(Float::neg_infinity::().classify(), FPInfinite); + assert_eq!(Zero::zero::().classify(), FPZero); + assert_eq!(Float::neg_zero::().classify(), FPZero); + assert_eq!(1f32.classify(), FPNormal); + assert_eq!(1e-37f32.classify(), FPNormal); + assert_eq!(1e-38f32.classify(), FPSubnormal); + } } diff --git a/src/libcore/num/f64.rs b/src/libcore/num/f64.rs index 7c6246757cd8..30c101fe8a9c 100644 --- a/src/libcore/num/f64.rs +++ b/src/libcore/num/f64.rs @@ -12,6 +12,7 @@ use libc::c_int; use num::{Zero, One, strconv}; +use num::{FPCategory, FPNaN, FPInfinite , FPZero, FPSubnormal, FPNormal}; use prelude::*; pub use cmath::c_double_targ_consts::*; @@ -611,12 +612,39 @@ impl Float for f64 { *self == Float::infinity() || *self == Float::neg_infinity() } - /// Returns `true` if the number is not infinite or NaN + /// Returns `true` if the number is neither infinite or NaN #[inline(always)] fn is_finite(&self) -> bool { !(self.is_NaN() || self.is_infinite()) } + /// Returns `true` if the number is neither zero, infinite, subnormal or NaN + #[inline(always)] + fn is_normal(&self) -> bool { + match self.classify() { + FPNormal => true, + _ => false, + } + } + + /// 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 { + static EXP_MASK: u64 = 0x7ff0000000000000; + static MAN_MASK: u64 = 0x000fffffffffffff; + + match ( + unsafe { ::cast::transmute::(*self) } & EXP_MASK, + unsafe { ::cast::transmute::(*self) } & MAN_MASK + ) { + (EXP_MASK, 0) => FPInfinite, + (EXP_MASK, _) => FPNaN, + (exp, _) if exp != 0 => FPNormal, + _ if self.is_zero() => FPZero, + _ => FPSubnormal, + } + } + #[inline(always)] fn mantissa_digits() -> uint { 53 } @@ -889,6 +917,7 @@ impl num::FromStrRadix for f64 { #[cfg(test)] mod tests { use f64::*; + use num::*; use super::*; use prelude::*; @@ -1088,4 +1117,27 @@ mod tests { assert_eq!(Primitive::bits::(), sys::size_of::() * 8); assert_eq!(Primitive::bytes::(), sys::size_of::()); } + + #[test] + fn test_is_normal() { + assert!(!Float::NaN::().is_normal()); + assert!(!Float::infinity::().is_normal()); + assert!(!Float::neg_infinity::().is_normal()); + assert!(!Zero::zero::().is_normal()); + assert!(!Float::neg_zero::().is_normal()); + assert!(1f64.is_normal()); + assert!(1e-307f64.is_normal()); + assert!(!1e-308f64.is_normal()); + } + + #[test] + fn test_classify() { + assert_eq!(Float::NaN::().classify(), FPNaN); + assert_eq!(Float::infinity::().classify(), FPInfinite); + assert_eq!(Float::neg_infinity::().classify(), FPInfinite); + assert_eq!(Zero::zero::().classify(), FPZero); + assert_eq!(Float::neg_zero::().classify(), FPZero); + assert_eq!(1e-307f64.classify(), FPNormal); + assert_eq!(1e-308f64.classify(), FPSubnormal); + } } diff --git a/src/libcore/num/float.rs b/src/libcore/num/float.rs index 35e113094876..9c3d30be0d49 100644 --- a/src/libcore/num/float.rs +++ b/src/libcore/num/float.rs @@ -22,6 +22,7 @@ use libc::c_int; use num::{Zero, One, strconv}; +use num::FPCategory; use prelude::*; pub use f64::{add, sub, mul, div, rem, lt, le, eq, ne, ge, gt}; @@ -782,32 +783,37 @@ impl Primitive for float { impl Float for float { #[inline(always)] - fn NaN() -> float { 0.0 / 0.0 } + fn NaN() -> float { Float::NaN::() as float } #[inline(always)] - fn infinity() -> float { 1.0 / 0.0 } + fn infinity() -> float { Float::infinity::() as float } #[inline(always)] - fn neg_infinity() -> float { -1.0 / 0.0 } + fn neg_infinity() -> float { Float::neg_infinity::() as float } #[inline(always)] - fn neg_zero() -> float { -0.0 } + fn neg_zero() -> float { Float::neg_zero::() as float } /// Returns `true` if the number is NaN #[inline(always)] - fn is_NaN(&self) -> bool { *self != *self } + fn is_NaN(&self) -> bool { (*self as f64).is_NaN() } /// Returns `true` if the number is infinite #[inline(always)] - fn is_infinite(&self) -> bool { - *self == Float::infinity() || *self == Float::neg_infinity() - } + fn is_infinite(&self) -> bool { (*self as f64).is_infinite() } - /// Returns `true` if the number is not infinite or NaN + /// Returns `true` if the number is neither infinite or NaN #[inline(always)] - fn is_finite(&self) -> bool { - !(self.is_NaN() || self.is_infinite()) - } + fn is_finite(&self) -> bool { (*self as f64).is_finite() } + + /// Returns `true` if the number is neither zero, infinite, subnormal or NaN + #[inline(always)] + fn is_normal(&self) -> bool { (*self as f64).is_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(always)] + fn classify(&self) -> FPCategory { (*self as f64).classify() } #[inline(always)] fn mantissa_digits() -> uint { Float::mantissa_digits::() } @@ -844,9 +850,7 @@ impl Float for float { /// than if the operations were performed separately /// #[inline(always)] - fn ln_1p(&self) -> float { - (*self as f64).ln_1p() as float - } + fn ln_1p(&self) -> float { (*self as f64).ln_1p() as float } /// /// Fused multiply-add. Computes `(self * a) + b` with only one rounding error. This @@ -867,6 +871,7 @@ impl Float for float { #[cfg(test)] mod tests { + use num::*; use super::*; use prelude::*; @@ -1063,6 +1068,30 @@ mod tests { assert_eq!(Primitive::bytes::(), sys::size_of::()); } + #[test] + fn test_is_normal() { + assert!(!Float::NaN::().is_normal()); + assert!(!Float::infinity::().is_normal()); + assert!(!Float::neg_infinity::().is_normal()); + assert!(!Zero::zero::().is_normal()); + assert!(!Float::neg_zero::().is_normal()); + assert!(1f.is_normal()); + assert!(1e-307f.is_normal()); + assert!(!1e-308f.is_normal()); + } + + #[test] + fn test_classify() { + assert_eq!(Float::NaN::().classify(), FPNaN); + assert_eq!(Float::infinity::().classify(), FPInfinite); + assert_eq!(Float::neg_infinity::().classify(), FPInfinite); + assert_eq!(Zero::zero::().classify(), FPZero); + assert_eq!(Float::neg_zero::().classify(), FPZero); + assert_eq!(1f.classify(), FPNormal); + assert_eq!(1e-307f.classify(), FPNormal); + assert_eq!(1e-308f.classify(), FPSubnormal); + } + #[test] pub fn test_to_str_exact_do_decimal() { let s = to_str_exact(5.0, 4u); diff --git a/src/libcore/num/num.rs b/src/libcore/num/num.rs index 7a71729e3e73..50ba55039d40 100644 --- a/src/libcore/num/num.rs +++ b/src/libcore/num/num.rs @@ -237,6 +237,23 @@ pub trait Int: Integer + Bitwise + BitCount {} +/// +/// Used for representing the classification of floating point numbers +/// +#[deriving(Eq)] +pub enum FPCategory { + /// "Not a Number", often obtained by dividing by zero + FPNaN, + /// Positive or negative infinity + FPInfinite , + /// Positive or negative zero + FPZero, + /// De-normalized floating point representation (less precise than `FPNormal`) + FPSubnormal, + /// A regular floating point number + FPNormal, +} + /// /// Primitive floating point numbers /// @@ -253,6 +270,8 @@ pub trait Float: Real fn is_NaN(&self) -> bool; fn is_infinite(&self) -> bool; fn is_finite(&self) -> bool; + fn is_normal(&self) -> bool; + fn classify(&self) -> FPCategory; fn mantissa_digits() -> uint; fn digits() -> uint;