aurum-numeric 0.2.0

Numeric traits
Documentation
// Copyright (c) 2016-2017 <daggerbot@gmail.com>
// This software is available under the terms of the zlib license.
// See COPYING.md for more information.

use std::{i8, i16, i32, i64, isize, u8, u16, u32, u64, usize, f32, f64};
use std::fmt::{self, Display, Formatter};
use std::mem;

/// Error which may occur when attempting to interpolate between clamped values of different types.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ClampError {
    Nan,
    Min,
    Max,
}

impl ClampError {
    fn as_str (self) -> &'static str {
        match self {
            ClampError::Nan => "value is not a number",
            ClampError::Min => "value is below clamped range",
            ClampError::Max => "value is above clamped range",
        }
    }
}

impl Display for ClampError {
    fn fmt (&self, f: &mut Formatter) -> fmt::Result {
        f.write_str(self.as_str())
    }
}

#[cfg(feature = "std")]
impl ::std::error::Error for ClampError {
    fn description (&self) -> &str { self.as_str() }
}

/// Trait for scalar values which have a "clamped" range.
///
/// For integer values, the clamped range is from 0 to `$ty::MAX`.
/// For floating point values, the clamped range is from 0.0 to 1.0.
pub trait Clamp: Sized + PartialOrd {
    fn clamp_min () -> Self;
    fn clamp_max () -> Self;

    fn clamp (self) -> Self {
        let min = Self::clamp_min();

        if self.is_clamp_nan() || self < min {
            return min;
        }

        let max = Self::clamp_max();

        if self > max {
            max
        } else {
            self
        }
    }

    fn is_clamp_nan (&self) -> bool;
}

macro_rules! impl_clamp_int {
    ($($ty:ident),*) => { $(
        impl Clamp for $ty {
            #[inline(always)]
            fn clamp_min () -> $ty { 0 }
            
            #[inline(always)]
            fn clamp_max () -> $ty { $ty::MAX }

            #[inline(always)]
            fn clamp (self) -> $ty { if self < 0 { 0 } else { self }}

            #[inline(always)]
            fn is_clamp_nan (&self) -> bool { false }
        }
    )* };
}

macro_rules! impl_clamp_uint {
    ($($ty:ident),*) => { $(
        impl Clamp for $ty {
            #[inline(always)]
            fn clamp_min () -> $ty { 0 }
            
            #[inline(always)]
            fn clamp_max () -> $ty { $ty::MAX }

            #[inline(always)]
            fn clamp (self) -> $ty { self }

            #[inline(always)]
            fn is_clamp_nan (&self) -> bool { false }
        }
    )* };
}

macro_rules! impl_clamp_float {
    ($($ty:ident),*) => { $(
        impl Clamp for $ty {
            #[inline(always)]
            fn clamp_min () -> $ty { 0.0 }
            
            #[inline(always)]
            fn clamp_max () -> $ty { 1.0 }

            #[inline(always)]
            fn is_clamp_nan (&self) -> bool { self.is_nan() }
        }
    )* };
}

impl_clamp_int!(i8, i16, i32, i64, isize);
impl_clamp_uint!(u8, u16, u32, u64, usize);
impl_clamp_float!(f32, f64);

/// Trait for interpolation of clamped values between different types.
pub trait ClampFrom<F: Sized>: Sized {
    fn clamp_from (other: F) -> Self;
    fn saturating_clamp_from (other: F) -> Self;
    fn try_clamp_from (other: F) -> Result<Self, ClampError>;
}

impl<T: Clamp> ClampFrom<T> for T {
    #[inline(always)]
    fn clamp_from (other: T) -> T { other }

    fn saturating_clamp_from (other: T) -> T {
        let min = T::clamp_min();

        if other.is_clamp_nan() || other < min {
            return min;
        }

        let max = T::clamp_max();

        if other > max {
            max
        } else {
            other
        }
    }

    #[inline(always)]
    fn try_clamp_from (other: T) -> Result<T, ClampError> {
        if other.is_clamp_nan() {
            Err(ClampError::Nan)
        } else if other < T::clamp_min() {
            Err(ClampError::Min)
        } else if other > T::clamp_max() {
            Err(ClampError::Max)
        } else {
            Ok(other)
        }
    }
}

macro_rules! impl_clamp_from_int_to_float {
    { $($f:ident => $t:ty,)* } => { $(
        impl ClampFrom<$f> for $t {
            #[inline(always)]
            fn clamp_from (other: $f) -> $t { other as $t / $f::MAX as $t }

            fn saturating_clamp_from (other: $f) -> $t {
                if other < 0 {
                    0.0
                } else {
                    other as $t / $f::MAX as $t
                }
            }

            fn try_clamp_from (other: $f) -> Result<$t, ClampError> {
                if other < 0 {
                    Err(ClampError::Min)
                } else {
                    Ok(other as $t / $f::MAX as $t)
                }
            }
        }
    )* };
}

macro_rules! impl_clamp_from_uint_to_float {
    { $($f:ident => $t:ty,)* } => { $(
        impl ClampFrom<$f> for $t {
            #[inline(always)]
            fn clamp_from (other: $f) -> $t { other as $t / $f::MAX as $t }

            #[inline(always)]
            fn saturating_clamp_from (other: $f) -> $t { other as $t / $f::MAX as $t }

            #[inline(always)]
            fn try_clamp_from (other: $f) -> Result<$t, ClampError> {
                Ok(other as $t / $f::MAX as $t)
            }
        }
    )* };
}

macro_rules! impl_clamp_from_float_to_int {
    { $($f:ty => $t:ident,)* } => { $(
        impl ClampFrom<$f> for $t {
            #[inline(always)]
            fn clamp_from (other: $f) -> $t { (other * $t::MAX as $f) as $t }

            fn saturating_clamp_from (other: $f) -> $t {
                if other.is_clamp_nan() || other < 0.0 {
                    0
                } else if other > 1.0 {
                    $t::MAX
                } else {
                    (other * $t::MAX as $f) as $t
                }
            }

            fn try_clamp_from (other: $f) -> Result<$t, ClampError> {
                if other.is_clamp_nan() {
                    Err(ClampError::Nan)
                } else if other < 0.0 {
                    Err(ClampError::Min)
                } else if other > 1.0 {
                    Err(ClampError::Max)
                } else {
                    Ok((other * $t::MAX as $f) as $t)
                }
            }
        }
    )* };
}

macro_rules! impl_clamp_from_float_to_float {
    { $($f:ty => $t:ty,)* } => { $(
        impl ClampFrom<$f> for $t {
            #[inline(always)]
            fn clamp_from (other: $f) -> $t { other as $t }

            fn saturating_clamp_from (other: $f) -> $t {
                if other.is_clamp_nan() || other < 0.0 {
                    0.0
                } else if other > 1.0 {
                    1.0
                } else {
                    other as $t
                }
            }

            fn try_clamp_from (other: $f) -> Result<$t, ClampError> {
                if other.is_clamp_nan() {
                    Err(ClampError::Nan)
                } else if other < 0.0 {
                    Err(ClampError::Min)
                } else if other > 1.0 {
                    Err(ClampError::Max)
                } else {
                    Ok(other as $t)
                }
            }
        }
    )* };
}

macro_rules! impl_clamp_from_uint_expand {
    { $($f:ident($mul:expr) => $t:ty,)* } => { $(
        impl ClampFrom<$f> for $t {
            #[inline(always)]
            fn clamp_from (other: $f) -> $t { other as $t * ($mul) }

            #[inline(always)]
            fn saturating_clamp_from (other: $f) -> $t { other as $t * ($mul) }

            #[inline(always)]
            fn try_clamp_from (other: $f) -> Result<$t, ClampError> { Ok(other as $t * ($mul)) }
        }
    )* };
}

macro_rules! impl_clamp_from_uint_shrink {
    { $($f:ident($shift:expr) => $t:ty,)* } => { $(
        impl ClampFrom<$f> for $t {
            #[inline(always)]
            fn clamp_from (other: $f) -> $t { (other >> ($shift)) as $t }

            #[inline(always)]
            fn saturating_clamp_from (other: $f) -> $t { (other >> ($shift)) as $t }

            #[inline(always)]
            fn try_clamp_from (other: $f) -> Result<$t, ClampError> {
                Ok((other >> ($shift)) as $t)
            }
        }
    )* };
}

impl_clamp_from_int_to_float! {
    i8 => f32,
    i8 => f64,
    i16 => f32,
    i16 => f64,
    i32 => f32,
    i32 => f64,
    i64 => f32,
    i64 => f64,
    isize => f32,
    isize => f64,
}

impl_clamp_from_uint_to_float! {
    u8 => f32,
    u8 => f64,
    u16 => f32,
    u16 => f64,
    u32 => f32,
    u32 => f64,
    u64 => f32,
    u64 => f64,
    usize => f32,
    usize => f64,
}

impl_clamp_from_float_to_int! {
    f32 => i8,
    f32 => i16,
    f32 => i32,
    f32 => i64,
    f32 => isize,
    f32 => u8,
    f32 => u16,
    f32 => u32,
    f32 => u64,
    f32 => usize,
    f64 => i8,
    f64 => i16,
    f64 => i32,
    f64 => i64,
    f64 => isize,
    f64 => u8,
    f64 => u16,
    f64 => u32,
    f64 => u64,
    f64 => usize,
}

impl_clamp_from_float_to_float! {
    f32 => f64,
    f64 => f32,
}

// TODO: impl_clamp_from_int_expand
// TODO: impl_clamp_from_int_shrink

impl_clamp_from_uint_expand! {
    u8(0x0101) => u16,
    u8(0x0101_0101) => u32,
    u8(0x0101_0101_0101_0101) => u64,
    u8(0x0101_0101_0101_0101u64 as usize) => usize,
    u16(0x0001_0001) => u32,
    u16(0x0001_0001_0001_0001) => u64,
    u16(0x0001_0001_0001_0001u64 as usize) => usize,
    u32(0x0000_0001_0000_0001) => u64,
    u32(0x0000_0001_0000_0001u64 as usize) => usize,
    usize((usize::MAX as u64).wrapping_add(2)) => u64,
}

impl_clamp_from_uint_shrink! {
    u16(8) => u8,
    u32(24) => u8,
    u32(16) => u16,
    u64(56) => u8,
    u64(48) => u16,
    u64(32) => u32,
    u64(64 - mem::size_of::<usize>() * 8) => usize,
    usize(mem::size_of::<usize>() * 8 - 8) => u8,
    usize(mem::size_of::<usize>() * 8 - 16) => u16,
    usize(mem::size_of::<usize>() * 8 - 32) => u32,
}

/// Blanket impl over `ClampFrom`.
pub trait ClampInto<T: Sized> : Sized {
    fn clamp_into (self) -> T;
    fn saturating_clamp_into (self) -> T;
    fn try_clamp_into (self) -> Result<T, ClampError>;
}

impl<F: Sized, T: ClampFrom<F>> ClampInto<T> for F {
    #[inline(always)]
    fn clamp_into (self) -> T { T::clamp_from(self) }

    #[inline(always)]
    fn saturating_clamp_into (self) -> T { T::saturating_clamp_from(self) }

    #[inline(always)]
    fn try_clamp_into (self) -> Result<T, ClampError> {
        T::try_clamp_from(self)
    }
}