Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement CoreFloat trait #32

Merged
merged 5 commits into from
Feb 7, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/cast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
260 changes: 240 additions & 20 deletions src/float.rs
Original file line number Diff line number Diff line change
@@ -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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#[inline] has no effect without a function body -- see rust-lang/rust#47475. That should be moved to the actual implementations.


/// 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`.
Expand Down Expand Up @@ -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);

Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we may have the same concern as Real did here. If someone does use num_traits::*, then the common methods between Float and CoreFloat may be ambiguous. Would you mind if we didn't re-export this in the root?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure supporting wildcard imports is worthwhile. If you want to make sure not to break them, you can't add any new functions or types. However, in this case I guess it is easy enough to avoid that problem.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure either. I guess a wildcard making us self-conflicting is a worse than having a conflict with some unrelated crate though.

pub use identities::{Zero, One, zero, one};
pub use ops::checked::{CheckedAdd, CheckedSub, CheckedMul, CheckedDiv, CheckedShl, CheckedShr};
Expand Down
38 changes: 4 additions & 34 deletions src/sign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand Down Expand Up @@ -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.
Expand All @@ -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`
Expand Down