1use std::fmt;
2use std::ops;
3
4pub fn percent(percentage: u8) -> Ratio {
16 Ratio::from_percentage(percentage)
17}
18
19#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
20pub struct Ratio(u8);
24
25impl Ratio {
26 pub fn from_percentage(percentage: u8) -> Self {
27 assert!(percentage <= 100, "Invalid value for percentage");
28
29 Ratio::from_f32(percentage as f32 / 100.0)
30 }
31
32 pub fn from_u8(value: u8) -> Self {
33 Ratio(value)
34 }
35
36 pub fn from_f32(float: f32) -> Self {
37 assert!(float >= 0.0, "Invalid ratio for type f32");
38 assert!(float <= 1.0, "Invalid ratio for type f32");
39
40 Ratio((float * 255.0).round() as u8)
41 }
42
43 pub fn as_percentage(self) -> u8 {
44 (self.0 as f32 / 255.0 * 100.0).round() as u8
45 }
46
47 pub fn as_u8(self) -> u8 {
48 self.0
49 }
50
51 pub fn as_f32(self) -> f32 {
52 self.0 as f32 / 255.0
53 }
54}
55
56impl fmt::Display for Ratio {
57 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
58 write!(f, "{}%", self.as_percentage())
59 }
60}
61
62impl ops::Add for Ratio {
63 type Output = Ratio;
64
65 fn add(self, other: Ratio) -> Ratio {
66 clamp_ratio(self.as_f32() + other.as_f32())
67 }
68}
69
70impl ops::Sub for Ratio {
71 type Output = Ratio;
72
73 fn sub(self, other: Ratio) -> Ratio {
74 clamp_ratio(self.as_f32() - other.as_f32())
75 }
76}
77
78impl ops::Mul for Ratio {
79 type Output = Ratio;
80
81 fn mul(self, other: Ratio) -> Ratio {
82 clamp_ratio(self.as_f32() * other.as_f32())
83 }
84}
85
86impl ops::Div for Ratio {
87 type Output = Ratio;
88
89 fn div(self, other: Ratio) -> Ratio {
90 clamp_ratio(self.as_f32() / other.as_f32())
91 }
92}
93
94fn clamp_ratio(value: f32) -> Ratio {
96 if value > 1.0 {
97 Ratio::from_f32(1.0)
98 } else if value >= 0.0 && value <= 1.0 {
99 Ratio::from_f32(value)
100 } else {
101 Ratio::from_f32(0.0)
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use Ratio;
108
109 #[test]
110 #[should_panic]
111 fn handles_invalid_percentage() {
112 Ratio::from_percentage(101);
113 }
114
115 #[test]
116 #[should_panic]
117 fn handles_invalid_f32() {
118 Ratio::from_f32(1.01);
119 }
120
121 #[test]
122 fn can_clamp_percentage() {
123 assert_eq!(
124 Ratio::from_percentage(50) + Ratio::from_percentage(55),
125 Ratio::from_percentage(100)
126 );
127 assert_eq!(
128 Ratio::from_percentage(50) - Ratio::from_percentage(55),
129 Ratio::from_percentage(0)
130 );
131 assert_eq!(
132 Ratio::from_percentage(55) / Ratio::from_percentage(50),
133 Ratio::from_percentage(100)
134 );
135 }
136
137 #[test]
138 fn can_clamp_f32() {
139 assert_eq!(
140 Ratio::from_f32(0.75) + Ratio::from_f32(0.75),
141 Ratio::from_f32(1.0)
142 );
143 assert_eq!(
144 Ratio::from_f32(0.25) - Ratio::from_f32(0.75),
145 Ratio::from_f32(0.0)
146 );
147 assert_eq!(
148 Ratio::from_f32(0.75) / Ratio::from_f32(0.25),
149 Ratio::from_f32(1.0)
150 );
151 }
152
153 #[test]
154 fn adds_percentage() {
155 let a = Ratio::from_percentage(55);
156 let b = Ratio::from_percentage(45);
157 let c = Ratio::from_percentage(10);
158
159 assert_eq!(a + b, Ratio::from_percentage(100));
160 assert_eq!(a + c, Ratio::from_percentage(65));
161 }
162
163 #[test]
164 fn subtracts_percentage() {
165 let a = Ratio::from_percentage(45);
166 let b = Ratio::from_percentage(10);
167 let c = Ratio::from_percentage(1);
168
169 assert_eq!(a - b, Ratio::from_percentage(35));
170 assert_eq!(b - c, Ratio::from_percentage(9));
171 }
172
173 #[test]
174 fn multiplies_percentage() {
175 let a = Ratio::from_percentage(100);
176 let b = Ratio::from_percentage(50);
177 let c = Ratio::from_percentage(20);
178
179 assert_eq!(a * a, Ratio::from_percentage(100));
180 assert_eq!(b * b, Ratio::from_percentage(25));
181 assert_eq!(c * c, Ratio::from_percentage(4));
182
183 assert_eq!(a * b, Ratio::from_percentage(50));
184 assert_eq!(b * a, Ratio::from_percentage(50));
185
186 assert_eq!(a * c, Ratio::from_percentage(20));
187 assert_eq!(c * a, Ratio::from_percentage(20));
188
189 assert_eq!(b * c, Ratio::from_percentage(10));
190 assert_eq!(c * b, Ratio::from_percentage(10));
191 }
192
193 #[test]
194 fn divides_percentage() {
195 let a = Ratio::from_percentage(100);
196 let b = Ratio::from_percentage(50);
197 let c = Ratio::from_percentage(20);
198
199 assert_eq!(a / a, Ratio::from_percentage(100));
200 assert_eq!(b / b, Ratio::from_percentage(100));
201 assert_eq!(c / c, Ratio::from_percentage(100));
202
203 assert_eq!(b / a, Ratio::from_percentage(50));
204 assert_eq!(c / a, Ratio::from_percentage(20));
205 assert_eq!(c / b, Ratio::from_percentage(40));
206 }
207
208 #[test]
209 fn adds_f32() {
210 let a = Ratio::from_f32(0.55);
211 let b = Ratio::from_f32(0.45);
212 let c = Ratio::from_f32(0.10);
213
214 assert_eq!(a + b, Ratio::from_f32(1.0));
215 assert_eq!(c + c, Ratio::from_u8(52));
216 assert_eq!(b + c, Ratio::from_u8(141));
217 }
218
219 #[test]
220 fn subtracts_f32() {
221 let a = Ratio::from_f32(0.55);
222 let b = Ratio::from_f32(0.45);
223 let c = Ratio::from_f32(0.10);
224
225 assert_eq!(b - c, Ratio::from_f32(0.35));
226 assert_eq!(a - b, Ratio::from_u8(25));
227 assert_eq!(a - c, Ratio::from_u8(114));
228 }
229
230 #[test]
231 fn multiplies_f32() {
232 let a = Ratio::from_f32(0.5);
233 let b = Ratio::from_f32(0.25);
234
235 assert_eq!(a * b, Ratio::from_f32(0.125));
236 assert_eq!(a * a, Ratio::from_f32(0.25));
237 assert_eq!(b * b, Ratio::from_f32(0.0625));
238 }
239
240 #[test]
241 fn divides_f32() {
242 let a = Ratio::from_f32(0.25);
243 let b = Ratio::from_f32(0.50);
244 let c = Ratio::from_f32(1.00);
245
246 assert_eq!(a / b, Ratio::from_f32(0.5));
247 assert_eq!(a / c, Ratio::from_f32(0.25));
248 assert_eq!(b / c, Ratio::from_f32(0.5));
249 }
250}