1use patterns_macros::because;
2
3pub trait Floor: Sized {
4 const ZERO: Self;
5 const ONE: Self;
6 fn sub_to_floor(self, taken: Self) -> Self;
7 fn add_to_ceiling(self, given: Self) -> Self;
8 fn mul_to_ceiling(self, times: Self) -> Self;
9 fn checked_div(self, by: Self) -> Option<Self>;
10 fn checked_rem(self, by: Self) -> Option<Self>;
11}
12
13macro_rules! floor_for {
14 ($($t:ty),*) => {
15 $(
16 impl Floor for $t {
17 const ZERO: $t = 0;
18 const ONE: $t = 1;
19 fn sub_to_floor(self, taken: $t) -> $t {
20 self.saturating_sub(taken)
21 }
22 fn add_to_ceiling(self, given: $t) -> $t {
23 self.saturating_add(given)
24 }
25 fn mul_to_ceiling(self, times: $t) -> $t {
26 self.saturating_mul(times)
27 }
28 fn checked_div(self, by: $t) -> Option<$t> {
29 <$t>::checked_div(self, by)
30 }
31 fn checked_rem(self, by: $t) -> Option<$t> {
32 <$t>::checked_rem(self, by)
33 }
34 }
35 )*
36 };
37}
38
39floor_for!(u8, u16, u32, u64, u128, usize);
40
41pub fn reduce_by<T: Floor>(value: T, amount: T) -> T {
42 value.sub_to_floor(amount)
43}
44
45pub fn raise_by<T: Floor>(value: T, amount: T) -> T {
46 value.add_to_ceiling(amount)
47}
48
49pub fn mul<T: Floor>(value: T, times: T) -> T {
50 value.mul_to_ceiling(times)
51}
52
53pub fn div<T: Floor>(value: T, by: T) -> Option<T> {
54 value.checked_div(by)
55}
56
57pub fn rem<T: Floor>(value: T, by: T) -> Option<T> {
58 value.checked_rem(by)
59}
60because!(Floor, "the arithmetic an unsigned integer does at its own edges, gathered on one trait so every pattern that adds, takes, scales or divides answers the overflow and zero cases in one place; signed types are left out because stopping at zero and folding into a range are only true of a type with nothing under zero");
61because!(reduce_by, "subtraction that stops at the type floor, because a quantity that cannot go below zero should say so once here rather than at every call");
62because!(raise_by, "the mirror of reduce_by, stopping at the type ceiling so an overflow is a saturation rather than a panic or a wrap");
63because!(mul, "scaling one quantity by another, stopping at the type ceiling, because a rate times a term is money and a product that wraps to something smaller or ends the process is worse than one pinned at the top");
64because!(div, "integer division that answers None on a zero divisor, so the caller decides what an undefined quotient means instead of the process ending; the quotient truncates, and a fraction that matters is a value the spec states");
65because!(rem, "the remainder division discards, answering None on a zero divisor as div does, needed on its own wherever a cycle position or an evenness check is the fact");
66
67pub fn doubled<T: Floor + Copy>(value: T) -> T {
68 raise_by(value, value)
69}
70because!(doubled, "a value added to itself, which a binding cannot write, since two is not a literal it may use and a name added to itself is refused as a spelling of a multiple; the add saturates, so a doubling at the ceiling stays there rather than wrapping to nothing");
71
72pub fn half<T: Floor>(value: T) -> T {
73 div(value, raise_by(T::ONE, T::ONE)).unwrap_or(T::ZERO)
74}
75because!(half, "a value split in two, the other side of doubled, written here because two is not a literal a binding may write; the divisor is one added to one and so is never zero, which is why this answers with the value rather than with a maybe");
76
77pub fn away<T: Floor + Copy>(a: T, b: T) -> T {
78 raise_by(reduce_by(a, b), reduce_by(b, a))
79}
80because!(away, "how far apart two values are without first asking which is larger, since a subtraction that stops at the floor gives nothing in the wrong direction and the distance in the right one, so the two of them add to the distance whichever way round they came");