diff options
author | Ben Bridle <bridle.benjamin@gmail.com> | 2023-12-24 21:07:11 +1300 |
---|---|---|
committer | Ben Bridle <bridle.benjamin@gmail.com> | 2023-12-24 21:07:11 +1300 |
commit | 291bb4de2a2e5940fbebd8d400f35c1fc3b0c7a2 (patch) | |
tree | 57fc89b43b72779f8192fac890a983ad94c9f6d1 /src | |
download | proportion-9b455098f5057bf1ec16d02c017ab5a3e860fd5a.zip |
Diffstat (limited to 'src')
-rw-r--r-- | src/internal.rs | 90 | ||||
-rw-r--r-- | src/lib.rs | 8 | ||||
-rw-r--r-- | src/proportion.rs | 107 | ||||
-rw-r--r-- | src/tests.rs | 66 |
4 files changed, 271 insertions, 0 deletions
diff --git a/src/internal.rs b/src/internal.rs new file mode 100644 index 0000000..ca099a2 --- /dev/null +++ b/src/internal.rs @@ -0,0 +1,90 @@ +pub trait Internal: Copy + Clone + PartialEq + std::ops::Sub<Output = Self> { + const MIN: Self; + const MAX: Self; + + fn clamp(self) -> Self; + fn as_u8(self) -> u8; + fn as_f32(self) -> f32; + fn as_f64(self) -> f64; + + fn mul_u8(self, value: u8) -> u8; + fn mul_u16(self, value: u16) -> u16; + fn mul_u32(self, value: u32) -> u32; + fn mul_f32(self, value: f32) -> f32; + fn mul_f64(self, value: f64) -> f64; +} + +impl Internal for u8 { + const MIN: Self = u8::MIN; + const MAX: Self = u8::MAX; + + fn clamp(self) -> Self { + self + } + fn as_u8(self) -> u8 { + self + } + fn as_f32(self) -> f32 { + self as f32 / u8::MAX as f32 + } + fn as_f64(self) -> f64 { + self as f64 / u8::MAX as f64 + } + + fn mul_u8(self, value: u8) -> u8 { + (value as u16 * self as u16 / 255u16) as u8 + } + fn mul_u16(self, value: u16) -> u16 { + (value as u32 * self as u32 / 255u32) as u16 + } + fn mul_u32(self, value: u32) -> u32 { + (value as u64 * self as u64 / 255u64) as u32 + } + fn mul_f32(self, value: f32) -> f32 { + value * self.as_f32() + } + fn mul_f64(self, value: f64) -> f64 { + value * self.as_f64() + } +} + +macro_rules! impl_for_float { + ($type:ty) => { + impl Internal for $type { + const MIN: Self = 0.0; + const MAX: Self = 1.0; + + fn clamp(self) -> Self { + <$type>::clamp(self, <Self as Internal>::MIN, <Self as Internal>::MAX) + } + fn as_u8(self) -> u8 { + (self * 255.0) as u8 + } + fn as_f32(self) -> f32 { + self as f32 + } + fn as_f64(self) -> f64 { + self as f64 + } + + fn mul_u8(self, value: u8) -> u8 { + (value as Self * self) as u8 + } + fn mul_u16(self, value: u16) -> u16 { + (value as Self * self).round() as u16 + } + fn mul_u32(self, value: u32) -> u32 { + (value as Self * self).round() as u32 + } + fn mul_f32(self, value: f32) -> f32 { + value * self.as_f32() + } + fn mul_f64(self, value: f64) -> f64 { + value * self.as_f64() + } + } + }; +} + +impl_for_float!(f32); +impl_for_float!(f64); diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..fd5f93c --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,8 @@ +#[cfg(test)] +mod tests; + +mod internal; +mod proportion; + +pub use crate::proportion::Proportion; +pub use internal::Internal; diff --git a/src/proportion.rs b/src/proportion.rs new file mode 100644 index 0000000..aa8d1ae --- /dev/null +++ b/src/proportion.rs @@ -0,0 +1,107 @@ +use crate::*; + +#[derive(Copy, Clone)] +pub struct Proportion<T: Internal> { + pub(crate) value: T, +} + +impl<I: Internal> Proportion<I> { + pub fn new(value: I) -> Self { + Self { value: value.clamp() } + } + + pub const MIN: Self = Proportion { + value: <I as Internal>::MIN, + }; + pub const MAX: Self = Proportion { + value: <I as Internal>::MAX, + }; + pub const ZERO: Self = Self::MIN; + pub const ONE: Self = Self::MAX; + + pub fn set(&mut self, new_value: I) { + self.value = new_value.clamp(); + } + + pub fn is_min(&self) -> bool { + self.value == I::MIN + } + pub fn is_max(&self) -> bool { + self.value == I::MAX + } + + pub fn is_zero(&self) -> bool { + self.is_min() + } + pub fn is_one(&self) -> bool { + self.is_max() + } + + pub fn as_u8(&self) -> u8 { + self.value.as_u8() + } + pub fn as_f32(&self) -> f32 { + self.value.as_f32() + } + pub fn as_f64(&self) -> f64 { + self.value.as_f64() + } + + pub fn invert(&self) -> Self { + Self { + value: I::MAX - self.value, + } + } + + pub fn value(&self) -> I { + self.value + } +} + +macro_rules! impl_mul { + ($type:ty, $method:ident) => { + impl<I: Internal> std::ops::Mul<Proportion<I>> for $type { + type Output = $type; + fn mul(self, proportion: Proportion<I>) -> $type { + proportion.value.$method(self) + } + } + impl<I: Internal> std::ops::Mul<$type> for Proportion<I> { + type Output = $type; + fn mul(self, value: $type) -> $type { + self.value.$method(value) + } + } + impl<I: Internal> std::ops::Mul<&Proportion<I>> for $type { + type Output = $type; + fn mul(self, proportion: &Proportion<I>) -> $type { + proportion.value.$method(self) + } + } + impl<I: Internal> std::ops::Mul<$type> for &Proportion<I> { + type Output = $type; + fn mul(self, value: $type) -> $type { + self.value.$method(value) + } + } + }; +} + +impl_mul!(u8, mul_u8); +impl_mul!(u16, mul_u16); +impl_mul!(u32, mul_u32); +impl_mul!(f32, mul_f32); +impl_mul!(f64, mul_f64); + +use std::fmt::{Debug, Display, Formatter}; + +impl<T: Internal> Debug for Proportion<T> { + fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> { + write!(f, "Proportion {{ {:.2} }}", self.as_f32()) + } +} +impl<T: Internal> Display for Proportion<T> { + fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> { + write!(f, "{:.2}", self.as_f32()) + } +} diff --git a/src/tests.rs b/src/tests.rs new file mode 100644 index 0000000..28e9038 --- /dev/null +++ b/src/tests.rs @@ -0,0 +1,66 @@ +use crate::*; + +macro_rules! test_fn { + ($test_name:tt => $test_body:tt) => { + mini_paste::item! { + #[test] + fn $test_name() { + $test_body + } + } + }; +} + +macro_rules! test_edges_for { + ($vtype:ty, P[$ptype:ty]) => { + test_edges_for!($vtype, P[$ptype], <$vtype>::MIN, <$vtype>::MAX); + }; + ($vtype:ty, P[$ptype:ty], $vmin:expr, $vmax:expr) => { + test_fn!([< test_ $vtype _min_by_ $ptype _min >] => { + assert!($vmin * Proportion::<$ptype>::MIN == $vmin); + }); + test_fn!([< test_ $vtype _min_by_ $ptype _max >] => { + assert!($vmin * Proportion::<$ptype>::MAX == $vmin); + }); + test_fn!([< test_ $vtype _max_by_ $ptype _min >] => { + assert!($vmax * Proportion::<$ptype>::MIN == $vmin); + }); + test_fn!([< test_ $vtype _max_by_ $ptype _max >] => { + assert!($vmax * Proportion::<$ptype>::MAX == $vmax); + }); + }; +} + +macro_rules! test_edges_for_ptype { + (P[$ptype:ty]) => { + test_edges_for!(u8, P[$ptype]); + test_edges_for!(u16, P[$ptype]); + test_edges_for!(u32, P[$ptype]); + test_edges_for!(f32, P[$ptype], 0.0, 1.0); + test_edges_for!(f64, P[$ptype], 0.0, 1.0); + }; +} + +test_edges_for_ptype!(P[u8]); +test_edges_for_ptype!(P[f32]); +test_edges_for_ptype!(P[f64]); + +macro_rules! test_invert_for { + ($ptype:ty) => { + test_fn!([< test_ $ptype _min_invert >] => { + assert!(Proportion::<$ptype>::MIN.invert().value == Proportion::<$ptype>::MAX.value); + }); + test_fn!([< test_ $ptype _max_invert >] => { + assert!(Proportion::<$ptype>::MAX.invert().value == Proportion::<$ptype>::MIN.value); + }); + } +} + +test_invert_for!(u8); +test_invert_for!(f32); +test_invert_for!(f64); + +// TODO: Test MAX*MID, MIN*MID, MID*MID=QUARTER +// test_fn!(test_u8_max_by_u8_mid => { +// assert!(u8::MAX * Proportion::<u8>::new(128) == 128u8); +// }); |