#![deny(missing_docs)]
#![deny(rustdoc::broken_intra_doc_links)]
#![deny(rustdoc::invalid_rust_codeblocks)]
#![deny(rustdoc::missing_crate_level_docs)]
#![warn(rustdoc::invalid_codeblock_attributes)]
use core::borrow::Borrow;
use core::error;
use core::fmt::{self, Debug, Display};
use core::ops::{Add, Div, Mul, Sub};
use num_traits::{float::FloatCore, One, Zero};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct UnitInterval<T>(T);
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct BoundError<T> {
kind: BoundErrorKind,
val: T,
}
impl<T> BoundError<T> {
#[inline]
const fn new(kind: BoundErrorKind, val: T) -> Self {
Self { kind, val }
}
#[inline]
const fn less_than_zero(val: T) -> Self {
Self::new(BoundErrorKind::LessThanZero, val)
}
#[inline]
const fn greater_than_one(val: T) -> Self {
Self::new(BoundErrorKind::GreaterThanOne, val)
}
#[inline]
const fn neither(val: T) -> Self {
Self::new(BoundErrorKind::Neither, val)
}
#[inline]
pub const fn kind(&self) -> BoundErrorKind {
self.kind
}
#[inline]
pub fn value(self) -> T {
self.val
}
}
impl<T: Debug> Debug for BoundError<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.kind {
BoundErrorKind::LessThanZero => {
write!(f, "{:?} compares less than zero", self.val)
}
BoundErrorKind::GreaterThanOne => {
write!(f, "{:?} compares greater than one", self.val)
}
BoundErrorKind::Neither => write!(
f,
"{:?} compares neither greater than or equal to zero nor less than or equal to one",
self.val
),
}
}
}
impl<T: Display> Display for BoundError<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.kind {
BoundErrorKind::LessThanZero => write!(f, "{} compares less than zero", self.val),
BoundErrorKind::GreaterThanOne => {
write!(f, "{} compares greater than one", self.val)
}
BoundErrorKind::Neither => write!(
f,
"{} compares neither greater than or equal to zero nor less than or equal to one",
self.val
),
}
}
}
impl<T: Debug + Display> error::Error for BoundError<T> {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BoundErrorKind {
LessThanZero,
GreaterThanOne,
Neither,
}
impl<T: Zero + One + PartialOrd> UnitInterval<T> {
#[inline]
pub fn new_checked(val: T) -> Result<Self, BoundError<T>> {
match (T::zero() <= val, val <= T::one()) {
(true, true) => Ok(Self(val)),
(true, false) => Err(BoundError::greater_than_one(val)),
(false, true) => Err(BoundError::less_than_zero(val)),
(false, false) => Err(BoundError::neither(val)),
}
}
#[inline]
#[must_use]
pub fn new_clamped(val: T) -> Self {
let zero = T::zero();
let one = T::one();
match (zero <= val, val <= one) {
(true, true) => Self(val),
(true, false) => Self(one),
(false, true) => Self(zero),
(false, false) => {
panic!("Value must compare greater equal to zero OR less equal to one")
}
}
}
#[inline]
#[must_use]
pub fn new(val: T) -> Self
where
T: Debug,
{
Self::new_checked(val).expect("Value must be in the interval [0, 1]")
}
}
impl<T: FloatCore> UnitInterval<T> {
#[inline]
#[must_use]
pub fn new_fract(val: T) -> Self {
Self(val.fract())
}
}
impl<T> UnitInterval<T> {
#[inline]
#[must_use]
pub const unsafe fn new_unchecked(val: T) -> Self {
Self(val)
}
#[inline]
pub const fn as_inner(&self) -> &T {
&self.0
}
#[inline]
pub fn into_inner(self) -> T {
self.0
}
}
impl<T: Zero> UnitInterval<T> {
#[inline]
pub fn zero() -> Self {
Self(T::zero())
}
}
impl<T: One> UnitInterval<T> {
#[inline]
pub fn one() -> Self {
Self(T::one())
}
#[inline]
pub fn complement(self) -> UnitInterval<<T as Sub>::Output>
where
T: Sub,
{
UnitInterval(T::one() - self.0)
}
}
impl<T: Add> UnitInterval<T>
where
T::Output: Zero + One + PartialOrd,
{
#[inline]
pub fn checked_add(self, rhs: Self) -> Result<UnitInterval<T::Output>, BoundError<T::Output>> {
UnitInterval::new_checked(self.0 + rhs.0)
}
#[inline]
pub fn clamped_add(self, rhs: Self) -> UnitInterval<T::Output> {
UnitInterval::new_clamped(self.0 + rhs.0)
}
}
impl<T: Sub> UnitInterval<T>
where
T::Output: Zero + One + PartialOrd,
{
#[inline]
pub fn checked_sub(self, rhs: Self) -> Result<UnitInterval<T::Output>, BoundError<T::Output>> {
UnitInterval::new_checked(self.0 - rhs.0)
}
#[inline]
pub fn clamped_sub(self, rhs: Self) -> UnitInterval<T::Output> {
UnitInterval::new_clamped(self.0 - rhs.0)
}
}
impl<T: Mul> UnitInterval<T>
where
T::Output: Zero + One + PartialOrd,
{
#[inline]
pub fn checked_mul(self, rhs: Self) -> Result<UnitInterval<T::Output>, BoundError<T::Output>> {
UnitInterval::new_checked(self.0 * rhs.0)
}
#[inline]
pub fn clamped_mul(self, rhs: Self) -> UnitInterval<T::Output> {
UnitInterval::new_clamped(self.0 * rhs.0)
}
}
impl<T: Div> UnitInterval<T>
where
T::Output: Zero + One + PartialOrd,
{
#[inline]
pub fn checked_div(self, rhs: Self) -> Result<UnitInterval<T::Output>, BoundError<T::Output>> {
UnitInterval::new_checked(self.0 / rhs.0)
}
#[inline]
pub fn clamped_div(self, rhs: Self) -> UnitInterval<T::Output> {
UnitInterval::new_clamped(self.0 / rhs.0)
}
}
impl<T: One> One for UnitInterval<T> {
#[inline]
fn one() -> Self {
UnitInterval::one()
}
}
impl<T: Add> Add<T> for UnitInterval<T> {
type Output = T::Output;
#[inline]
fn add(self, rhs: T) -> Self::Output {
self.0 + rhs
}
}
impl<T: Sub> Sub<T> for UnitInterval<T> {
type Output = T::Output;
#[inline]
fn sub(self, rhs: T) -> Self::Output {
self.0 - rhs
}
}
impl<T: Mul> Mul<T> for UnitInterval<T> {
type Output = T::Output;
#[inline]
fn mul(self, rhs: T) -> Self::Output {
self.0 * rhs
}
}
impl<T: Div> Div<T> for UnitInterval<T> {
type Output = T::Output;
#[inline]
fn div(self, rhs: T) -> Self::Output {
self.0 / rhs
}
}
impl<T: Add> Add for UnitInterval<T> {
type Output = T::Output;
#[inline]
fn add(self, rhs: Self) -> Self::Output {
self.0 + rhs.0
}
}
impl<T: Sub> Sub for UnitInterval<T> {
type Output = T::Output;
#[inline]
fn sub(self, rhs: Self) -> Self::Output {
self.0 - rhs.0
}
}
impl<T: Mul> Mul for UnitInterval<T> {
type Output = UnitInterval<T::Output>;
#[inline]
fn mul(self, rhs: UnitInterval<T>) -> Self::Output {
UnitInterval(self.0 * rhs.0)
}
}
impl<T: Div> Div for UnitInterval<T> {
type Output = T::Output;
#[inline]
fn div(self, rhs: Self) -> Self::Output {
self.0 / rhs.0
}
}
impl<T> AsRef<T> for UnitInterval<T> {
#[inline]
fn as_ref(&self) -> &T {
self.as_inner()
}
}
impl<T> Borrow<T> for UnitInterval<T> {
#[inline]
fn borrow(&self) -> &T {
self.as_inner()
}
}
#[cfg(test)]
mod tests {
use super::*;
use core::cmp::Ordering;
use core::f64;
const EPSILON: f64 = 1e-4;
macro_rules! assert_float_eq {
($a:expr, $b:expr) => {
let left: f64 = $a;
let right: f64 = $b;
match (left - right).abs().partial_cmp(&EPSILON) {
None | Some(Ordering::Greater) => assert_eq!(left, right),
_ => {}
}
};
}
#[test]
fn new() {
assert_eq!(UnitInterval::new(0.5).into_inner(), 0.5);
assert_eq!(UnitInterval::new(0.0).into_inner(), 0.0);
assert_eq!(UnitInterval::new(1.0).into_inner(), 1.0);
}
#[test]
fn new_negative_zero() {
let _ = UnitInterval::new(-0.0);
}
#[test]
#[should_panic(expected = "Value must be in the interval [0, 1]: -0.1 compares less than zero")]
fn new_panic_less_than_zero() {
let _ = UnitInterval::new(-0.1);
}
#[test]
#[should_panic(
expected = "Value must be in the interval [0, 1]: 1.1 compares greater than one"
)]
fn new_panic_greater_than_one() {
let _ = UnitInterval::new(1.1);
}
#[test]
fn new_checked() {
assert!(UnitInterval::new_checked(0.5).is_ok());
assert!(UnitInterval::new_checked(0.0).is_ok());
assert!(UnitInterval::new_checked(1.0).is_ok());
let err = UnitInterval::new_checked(-0.1).unwrap_err();
assert_eq!(err.kind(), BoundErrorKind::LessThanZero);
assert_float_eq!(err.value(), -0.1);
let err = UnitInterval::new_checked(1.1).unwrap_err();
assert_eq!(err.kind(), BoundErrorKind::GreaterThanOne);
assert_float_eq!(err.value(), 1.1);
let err = UnitInterval::new_checked(f64::NAN).unwrap_err();
assert_eq!(err.kind(), BoundErrorKind::Neither);
assert!(err.value().is_nan());
}
#[test]
fn new_clamped() {
assert_float_eq!(UnitInterval::new_clamped(0.5).into_inner(), 0.5);
assert_float_eq!(UnitInterval::new_clamped(-0.1).into_inner(), 0.0);
assert_float_eq!(UnitInterval::new_clamped(1.1).into_inner(), 1.0);
}
#[test]
fn new_fract() {
assert_float_eq!(UnitInterval::new_fract(1.5).into_inner(), 0.5);
assert_float_eq!(UnitInterval::new_fract(2.75).into_inner(), 0.75);
assert_float_eq!(UnitInterval::new_fract(-1.25).into_inner(), -0.25);
}
#[test]
fn as_inner() {
let unit = UnitInterval::new(0.5);
assert_float_eq!(*unit.as_inner(), 0.5);
}
#[test]
fn into_inner() {
let unit = UnitInterval::new(0.5);
assert_float_eq!(unit.into_inner(), 0.5);
}
#[test]
fn zero() {
assert_float_eq!(UnitInterval::<f64>::zero().into_inner(), 0.0);
}
#[test]
fn one() {
assert_float_eq!(UnitInterval::<f64>::one().into_inner(), 1.0);
}
#[test]
fn complement() {
assert_float_eq!(UnitInterval::new(0.3).complement().into_inner(), 0.7);
assert_float_eq!(UnitInterval::new(1.0).complement().into_inner(), 0.0);
assert_float_eq!(UnitInterval::new(0.0).complement().into_inner(), 1.0);
}
#[test]
fn checked_add() {
let a = UnitInterval::new(0.3);
let b = UnitInterval::new(0.4);
assert_float_eq!(a.checked_add(b).unwrap().into_inner(), 0.7f64);
let a = UnitInterval::new(0.7);
let b = UnitInterval::new(0.4);
assert!(a.checked_add(b).is_err());
}
#[test]
fn checked_sub() {
let a = UnitInterval::new(0.7);
let b = UnitInterval::new(0.4);
assert_float_eq!(a.checked_sub(b).unwrap().into_inner(), 0.3);
let a = UnitInterval::new(0.3);
let b = UnitInterval::new(0.4);
assert!(a.checked_sub(b).is_err());
}
#[test]
fn checked_mul() {
let a = UnitInterval::new(0.5);
let b = UnitInterval::new(0.6);
assert_float_eq!(a.checked_mul(b).unwrap().into_inner(), 0.3);
}
#[test]
fn checked_div() {
let a = UnitInterval::new(0.5);
let b = UnitInterval::new(0.25);
assert!(a.checked_div(b).is_err());
let a = UnitInterval::new(0.25);
let b = UnitInterval::new(0.5);
assert_float_eq!(a.checked_div(b).unwrap().into_inner(), 0.5);
}
#[test]
fn clamped_add() {
let a = UnitInterval::new(0.7);
let b = UnitInterval::new(0.4);
assert_float_eq!(a.clamped_add(b).into_inner(), 1.0);
}
#[test]
fn clamped_sub() {
let a = UnitInterval::new(0.3);
let b = UnitInterval::new(0.4);
assert_float_eq!(a.clamped_sub(b).into_inner(), 0.0);
}
#[test]
fn clamped_mul() {
let a = UnitInterval::new(0.5);
let b = UnitInterval::new(0.6);
assert_float_eq!(a.clamped_mul(b).into_inner(), 0.3);
}
#[test]
fn clamped_div() {
let a = UnitInterval::new(0.5);
let b = UnitInterval::new(0.25);
assert_float_eq!(a.clamped_div(b).into_inner(), 1.0);
}
#[test]
fn add() {
let a = UnitInterval::new(0.3);
let b = UnitInterval::new(0.4);
assert_float_eq!(a + b, 0.7);
}
#[test]
fn sub() {
let a = UnitInterval::new(0.7);
let b = UnitInterval::new(0.4);
assert_float_eq!(a - b, 0.3);
}
#[test]
fn mul() {
let a = UnitInterval::new(0.5);
let b = UnitInterval::new(0.6);
assert_float_eq!((a * b).into_inner(), 0.3);
}
#[test]
fn div() {
let a = UnitInterval::new(0.5);
let b = UnitInterval::new(0.25);
assert_float_eq!(a / b, 2.0);
}
}