reduction_factor/lib.rs
1#![no_std]
2#![doc = include_str!("../README.md")]
3
4use core::fmt;
5use core::num::NonZeroUsize;
6use core::ops::{Add, Deref, DerefMut, Mul, MulAssign, Sub};
7
8use num_traits::{One, Zero};
9
10/// A newtype representing a reduction factor of `(1 - T)`.
11///
12/// See the [module-level documentation](self) for more information.
13///
14/// Note that this type does not implement the [`num_traits::One`](https://docs.rs/num-traits/latest/num_traits/identities/trait.One.html) trait.
15/// This is intentional as the trait requires `one()` to return the multiplicative identity, which for `Reduction` is [`Reduction::none()`].
16/// However, having `Reduction::one()` return a `Reduction(T::zero())` would be highly confusing.
17#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
18#[repr(transparent)]
19pub struct Reduction<T>(pub T);
20
21impl<T: fmt::Display> fmt::Display for Reduction<T> {
22 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23 write!(f, "(1 - {})", self.0)
24 }
25}
26
27impl<T> Reduction<T> {
28 /// Creates a new `Reduction` from the given inner value.
29 #[inline]
30 #[must_use]
31 pub const fn new(value: T) -> Self {
32 Self(value)
33 }
34
35 /// Creates a `Reduction` representing no reduction (0%).
36 ///
37 /// This is the multiplicative identity for composition: any reduction composed
38 /// with `none` remains unchanged.
39 ///
40 /// Its multiplier is 1.
41 #[must_use]
42 pub fn none() -> Self
43 where
44 T: Zero,
45 {
46 Self(T::zero())
47 }
48
49 /// Creates a `Reduction` representing a full reduction (100%).
50 ///
51 /// This is the absorbing element for composition: any reduction composed
52 /// with `full` results in `full`.
53 ///
54 /// Note that for floats, this property may not hold exactly.
55 ///
56 /// Its multiplier is 0.
57 #[must_use]
58 pub fn full() -> Self
59 where
60 T: One,
61 {
62 Self(T::one())
63 }
64
65 /// Consumes the `Reduction` and returns the inner value `x`.
66 #[inline]
67 pub fn inner(self) -> T {
68 self.0
69 }
70}
71impl<T: One + Sub<Output = T>> Reduction<T> {
72 /// Calculates the multiplicative factor, `1 - self.0`.
73 ///
74 /// This is the value that is multiplied with a base value when the reduction is applied.
75 ///
76 /// # Example
77 /// ```
78 /// use reduction_factor::Reduction;
79 /// let r = Reduction(0.25f32);
80 /// assert_eq!(r.multiplier(), 0.75);
81 /// ```
82 pub fn multiplier(self) -> T {
83 T::one() - self.0
84 }
85
86 /// Applies the reduction to a given value.
87 ///
88 /// This is equivalent to `value * self.multiplier()`.
89 ///
90 /// This operation is also available through multiplication: `reduction * value`.
91 ///
92 /// # Example
93 /// ```
94 /// use reduction_factor::Reduction;
95 /// let r = Reduction(0.25f32);
96 /// assert_eq!(r.reduce(100.0), 75.0);
97 /// assert_eq!(r * 100.0, 75.0);
98 /// ```
99 #[doc(alias = "apply")]
100 pub fn reduce(self, value: T) -> T {
101 value * self.multiplier()
102 }
103
104 /// Returns the complement of the reduction.
105 ///
106 /// The complement of `Reduction(x)` is `Reduction(1 - x)`.
107 /// For example, the complement of a 25% reduction is an 75% reduction.
108 ///
109 /// # Example
110 /// ```
111 /// use reduction_factor::Reduction;
112 /// let r = Reduction(0.25f32);
113 /// let complement = r.complement();
114 /// assert_eq!(*complement, 0.75);
115 /// ```
116 #[doc(alias = "invert")]
117 pub fn complement(self) -> Self {
118 Self(self.multiplier())
119 }
120}
121impl<T: Add<Output = T> + Sub<Output = T> + Mul<Output = T> + Clone> Reduction<T> {
122 /// Composes this reduction with another, returning a new `Reduction`.
123 ///
124 /// This is equivalent to applying one reduction and then the other.
125 ///
126 /// The formula for the new inner value is `x + y - xy` where
127 /// `x` and `y` are the inner values of the two reductions.
128 ///
129 /// This operation is also available through multiplication: `r1 * r2`.
130 ///
131 /// # Example
132 /// ```
133 /// use reduction_factor::Reduction;
134 /// let r1 = Reduction(0.20f32);
135 /// let r2 = Reduction(0.10f32);
136 /// let stacked = r1.compose(r2);
137 /// assert_eq!(*stacked, 0.28);
138 /// assert_eq!(r1 * r2, stacked); // Equivalent
139 /// ```
140 #[doc(alias = "stack")]
141 #[doc(alias = "combine")]
142 pub fn compose(self, other: Self) -> Self {
143 // (1 - x) * (1 - y) = 1 - x - y + xy = 1 - (x + y - xy)
144 Self(self.0.clone() + other.0.clone() - self.0 * other.0)
145 }
146
147 /// In-place version of [`compose`](Self::compose).
148 #[doc(alias = "stack_inplace")]
149 #[doc(alias = "combine_inplace")]
150 pub fn compose_inplace(&mut self, other: Self)
151 where
152 // `Default` bound is added because it's not worth dealing with the trouble of temporarily taking `T` out of a `&mut T`
153 T: Default,
154 {
155 let value = core::mem::take(&mut self.0);
156 self.0 = value.clone() + other.0.clone() - value * other.0;
157 }
158
159 /// Composes the reduction with itself `exponent` times.
160 ///
161 /// `pow(0)` returns `Reduction(0)` the identity reduction, [`Reduction::none()`].
162 ///
163 /// # Example
164 /// ```
165 /// use reduction_factor::Reduction;
166 /// let r = Reduction(0.5f32);
167 /// // Applying a 50% reduction twice results in a 75% reduction.
168 /// let r2 = r.pow(2);
169 /// assert_eq!(*r2, 0.75);
170 /// assert_eq!(r * r, r2);
171 /// ```
172 ///
173 /// Zero exponent returns the identity reduction.
174 /// ```
175 /// use reduction_factor::Reduction;
176 /// let r = Reduction(0.5f32);
177 ///
178 /// let r0 = r.pow(0);
179 /// assert_eq!(*r0, 0.0);
180 /// assert_eq!(r0, Reduction::none());
181 /// ```
182 #[doc(alias = "repeat")]
183 pub fn pow(&self, exponent: usize) -> Self
184 where
185 T: Zero,
186 {
187 if exponent == 0 {
188 Self::none()
189 } else if exponent % 2 == 0 {
190 let tmp = self.pow(exponent / 2);
191 tmp.clone().compose(tmp)
192 } else {
193 self.clone().compose(self.pow(exponent - 1))
194 }
195 }
196
197 /// Composes the reduction with itself `exponent` times for a non-zero exponent.
198 ///
199 /// The main difference from [`Reduction::pow()`] is that `T: Zero` isn't required.
200 ///
201 /// # Example
202 /// ```
203 /// use reduction_factor::Reduction;
204 /// use core::num::NonZeroUsize;
205 ///
206 /// let r = Reduction(0.5f32);
207 /// // Applying a 50% reduction twice results in a 75% reduction.
208 /// let r2 = r.pow_nonzero(NonZeroUsize::new(2).unwrap());
209 /// assert_eq!(*r2, 0.75);
210 /// assert_eq!(r * r, r2);
211 /// ```
212 #[doc(alias = "repeat_nonzero")]
213 pub fn pow_nonzero(&self, exponent: NonZeroUsize) -> Self {
214 if exponent.get() == 1 {
215 self.clone()
216 } else if exponent.get() % 2 == 0 {
217 // SAFETY: Since exponent can't be 1, it won't go to 0
218 let exponent = unsafe { NonZeroUsize::new_unchecked(exponent.get() / 2) };
219 let tmp = self.pow_nonzero(exponent);
220 tmp.clone().compose(tmp)
221 } else {
222 // SAFETY: Since exponent can't be 1, it won't go to 0
223 let exponent = unsafe { NonZeroUsize::new_unchecked(exponent.get() - 1) };
224 self.clone().compose(self.pow_nonzero(exponent))
225 }
226 }
227}
228
229impl<T> From<T> for Reduction<T> {
230 #[inline]
231 fn from(value: T) -> Self {
232 Self::new(value)
233 }
234}
235
236impl<T> Deref for Reduction<T> {
237 type Target = T;
238
239 #[inline]
240 fn deref(&self) -> &Self::Target {
241 &self.0
242 }
243}
244impl<T> DerefMut for Reduction<T> {
245 #[inline]
246 fn deref_mut(&mut self) -> &mut Self::Target {
247 &mut self.0
248 }
249}
250impl<T> AsRef<T> for Reduction<T> {
251 #[inline]
252 fn as_ref(&self) -> &T {
253 &self.0
254 }
255}
256impl<T> AsMut<T> for Reduction<T> {
257 #[inline]
258 fn as_mut(&mut self) -> &mut T {
259 &mut self.0
260 }
261}
262
263impl<T: Zero> Default for Reduction<T> {
264 #[inline]
265 fn default() -> Self {
266 Self::none()
267 }
268}
269
270impl<T: Add<Output = T> + Sub<Output = T> + Mul<Output = T> + Clone> Mul for Reduction<T> {
271 type Output = Self;
272
273 /// The multiplication operator is overloaded for two `Reduction`s to perform [`compose`](Self::compose).
274 ///
275 /// This is the idiomatic way to compose two reductions.
276 #[inline]
277 fn mul(self, rhs: Self) -> Self::Output {
278 self.compose(rhs)
279 }
280}
281impl<T: Default + Add<Output = T> + Sub<Output = T> + Mul<Output = T> + Clone> MulAssign
282 for Reduction<T>
283{
284 /// The `*=` operator is overloaded for two `Reduction`s to perform [`compose_inplace`](Self::compose_inplace).
285 #[inline]
286 fn mul_assign(&mut self, rhs: Self) {
287 self.compose_inplace(rhs)
288 }
289}
290impl<T: One + Sub<Output = T>> Mul<T> for Reduction<T> {
291 type Output = T;
292
293 /// The multiplication operator is overloaded to [`reduce`](Self::reduce) the reduction to a value of type `T`.
294 #[inline]
295 fn mul(self, rhs: T) -> Self::Output {
296 self.reduce(rhs)
297 }
298}
299
300// Does not implement `One` because `Reduction(1)` is not the multiplicative identity
301// (that's how `num_trait` defined its `One`).
302// The multiplicative identity is `Reduction(0)` or `Reduction::none()`.
303// But if `Reduction::one() == Reduction::none()`, that is way too confusing.
304
305// Cannot implement `Zero` because addition between `Reduction`s is not defined.
306// Even if it could be implemented, the same confusion would make it not worth it.
307
308#[cfg(test)]
309mod tests {
310 use super::*;
311 use core::num::NonZeroUsize;
312
313 const EPSILON: f32 = 1e-6;
314
315 #[test]
316 fn test_apply_reduction() {
317 let price = 100.0f32;
318 let discount = Reduction(0.25);
319 assert!((discount * price - 75.0).abs() < EPSILON);
320 }
321
322 #[test]
323 fn test_compose() {
324 let r1 = Reduction(0.20f32);
325 let r2 = Reduction(0.10f32);
326 let combined = r1 * r2;
327 assert!((combined.inner() - 0.28).abs() < EPSILON);
328 }
329
330 #[test]
331 fn test_identity_and_default() {
332 let r1 = Reduction(0.3f32);
333 assert_eq!(r1 * Reduction::none(), r1);
334 assert_eq!(Reduction::none() * r1, r1);
335 assert_eq!(Reduction::<f32>::default(), Reduction::none());
336 }
337
338 #[test]
339 fn test_full_reduction() {
340 let r1 = Reduction(0.3f32);
341 let full = Reduction::full();
342 assert!((*(r1 * full) - *full).abs() < EPSILON);
343 assert!((*(full * r1) - *full).abs() < EPSILON);
344 assert!((full * 100.0f32).abs() < EPSILON);
345 }
346
347 #[test]
348 fn test_pow() {
349 let r = Reduction(0.5f32);
350 assert_eq!(r.pow(0), Reduction::none());
351 assert_eq!(r.pow(1), r);
352 assert!((r.pow(2).inner() - 0.75).abs() < EPSILON); // 0.5 + 0.5 - 0.25 = 0.75
353 assert!((r.pow(3).inner() - 0.875).abs() < EPSILON);
354 }
355
356 #[test]
357 fn test_pow_nonzero() {
358 let r = Reduction(0.5f32);
359 assert_eq!(r.pow_nonzero(NonZeroUsize::new(1).unwrap()), r);
360 assert!((r.pow_nonzero(NonZeroUsize::new(2).unwrap()).inner() - 0.75).abs() < EPSILON);
361 }
362
363 #[test]
364 fn test_complement() {
365 let r = Reduction(0.2f32);
366 let c = r.complement();
367 assert!((c.inner() - 0.8).abs() < EPSILON);
368 assert!((c.complement().inner() - 0.2).abs() < EPSILON);
369 }
370}