1#![cfg_attr(not(test), no_std)]
2#![doc = include_str!("../README.md")]
3
4use core::cmp::{Eq, Ord, Ordering, PartialEq, PartialOrd};
5use core::fmt::{Display, Formatter};
6use core::hash::{Hash, Hasher};
7
8mod div;
9
10pub(crate) mod utils;
11
12pub use div::*;
13
14#[derive(Debug, Copy, Clone)]
21pub struct Ratio<N, D> {
22 pub n: N,
24
25 pub d: D,
27}
28
29macro_rules! impl_gcd {
30 ($f:ident, $T:ty) => {
31 #[inline]
35 const fn $f(mut a: $T, mut b: $T) -> $T {
36 while b > 0 {
37 let r = a % b;
38 a = b;
39 b = r;
40 }
41 a
42 }
43 };
44}
45
46impl_gcd!(gcd_u8, u8);
47impl_gcd!(gcd_u16, u16);
48impl_gcd!(gcd_u32, u32);
49impl_gcd!(gcd_u64, u64);
50
51pub trait ArithTypes {
55 type Min;
57
58 type Max;
60
61 type Ext;
65}
66
67impl<N, D> Ratio<N, D> {
68 #[inline]
70 pub const fn new(n: N, d: D) -> Self {
71 Self { n, d }
72 }
73}
74
75macro_rules! impl_ratio {
76 ($N:ty, $D:ty, [$gcd:expr, $MIN: ty, $MAX:ty, $EXT:ty]) => {
77 impl ArithTypes for Ratio<$N, $D> {
78 type Min = $MIN;
79 type Max = $MAX;
80 type Ext = $EXT;
81 }
82
83 impl Ratio<$N, $D> {
84 pub const ZERO: Self = Self { n: 0, d: 0 };
85 pub const ONE: Self = Self { n: 1, d: 1 };
86
87 #[inline]
90 pub const fn is_zero(&self) -> bool {
91 self.n == 0 || self.d == 0
92 }
93
94 #[inline]
98 pub const fn is_one(&self) -> bool {
99 type Max = <Ratio<$N, $D> as ArithTypes>::Max;
100
101 !self.is_zero() && self.n as Max == self.d as Max
102 }
103
104 #[doc = concat!("type R = Ratio<", stringify!($N), ", ", stringify!($D), ">;")]
111 #[inline]
125 pub const fn const_cmp(&self, other: &Self) -> Ordering {
126 type Ext = <Ratio<$N, $D> as ArithTypes>::Ext;
127
128 match (self.is_zero(), other.is_zero()) {
129 (true, true) => return Ordering::Equal,
130 (true, false) => return Ordering::Less,
131 (false, true) => return Ordering::Greater,
132 (false, false) => (),
133 };
134
135 let lhs = (self.n as Ext) * (other.d as Ext);
136 let rhs = (other.n as Ext) * (self.d as Ext);
137 if lhs == rhs {
138 Ordering::Equal
139 } else if lhs < rhs {
140 Ordering::Less
141 } else {
142 Ordering::Greater
143 }
144 }
145
146 #[inline]
150 pub const fn lowest_form(
151 &self,
152 ) -> Ratio<<Self as ArithTypes>::Max, <Self as ArithTypes>::Max> {
153 type Max = <Ratio<$N, $D> as ArithTypes>::Max;
154
155 if self.is_zero() {
156 return Ratio::<Max, Max>::ZERO;
157 }
158 let n = self.n as Max;
159 let d = self.d as Max;
160 let gcd = $gcd(d, n);
162 Ratio {
164 n: n / gcd,
165 d: d / gcd,
166 }
167 }
168 }
169
170 impl Default for Ratio<$N, $D> {
171 #[inline]
172 fn default() -> Self {
173 Self::ZERO
174 }
175 }
176
177 impl PartialEq for Ratio<$N, $D> {
179 #[inline]
180 fn eq(&self, rhs: &Self) -> bool {
181 self.const_cmp(rhs).is_eq()
182 }
183 }
184
185 impl Eq for Ratio<$N, $D> {}
187
188 impl PartialOrd for Ratio<$N, $D> {
190 #[inline]
191 fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
192 Some(self.cmp(rhs))
193 }
194 }
195
196 impl Ord for Ratio<$N, $D> {
198 #[inline]
199 fn cmp(&self, rhs: &Self) -> Ordering {
200 self.const_cmp(rhs)
201 }
202 }
203
204 impl Hash for Ratio<$N, $D> {
210 #[inline]
211 fn hash<H>(&self, state: &mut H)
212 where
213 H: Hasher,
214 {
215 let Ratio { n, d } = self.lowest_form();
216 n.hash(state);
217 d.hash(state);
218 }
219 }
220
221 impl Display for Ratio<$N, $D> {
223 #[inline]
224 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
225 f.write_fmt(format_args!("{}/{}", self.n, self.d))
226 }
227 }
228 };
229}
230
231impl_ratio!(u8, u8, [gcd_u8, u8, u8, u16]);
232impl_ratio!(u8, u16, [gcd_u16, u8, u16, u32]);
233impl_ratio!(u8, u32, [gcd_u32, u8, u32, u64]);
234impl_ratio!(u8, u64, [gcd_u64, u8, u64, u128]);
235
236impl_ratio!(u16, u8, [gcd_u16, u8, u16, u32]);
237impl_ratio!(u16, u16, [gcd_u16, u16, u16, u32]);
238impl_ratio!(u16, u32, [gcd_u32, u16, u32, u64]);
239impl_ratio!(u16, u64, [gcd_u64, u16, u64, u128]);
240
241impl_ratio!(u32, u8, [gcd_u32, u8, u32, u64]);
242impl_ratio!(u32, u16, [gcd_u32, u16, u32, u64]);
243impl_ratio!(u32, u32, [gcd_u32, u32, u32, u64]);
244impl_ratio!(u32, u64, [gcd_u64, u32, u64, u128]);
245
246impl_ratio!(u64, u8, [gcd_u64, u8, u64, u128]);
247impl_ratio!(u64, u16, [gcd_u64, u16, u64, u128]);
248impl_ratio!(u64, u32, [gcd_u64, u32, u64, u128]);
249impl_ratio!(u64, u64, [gcd_u64, u64, u64, u128]);
250
251#[cfg(test)]
252mod tests {
253 use proptest::prelude::*;
254 use std::hash::DefaultHasher;
255
256 use super::*;
257
258 macro_rules! zero_eq {
259 ($N:ty, $D:ty, $zero:ident) => {
260 proptest! {
261 #[test]
262 fn $zero(a: $N, b: $D) {
263 type NR = Ratio<$N, $D>;
264 type DR = Ratio<$D, $N>;
265 type Max = <NR as ArithTypes>::Max;
266
267 for nr in [
268 NR::new(a, 0),
269 NR::new(0, b),
270 ] {
271 prop_assert_eq!(NR::ZERO, nr, "{} != 0", nr);
272 }
273
274 for dr in [
275 DR::new(b, 0),
276 DR::new(0, a),
277 ] {
278 prop_assert_eq!(DR::ZERO, dr, "{} != 0", dr);
279 }
280
281 for lowest_form in [
282 DR::new(0, a).lowest_form(),
283 NR::new(a, 0).lowest_form(),
284 NR::new(0, b).lowest_form(),
285 DR::new(b, 0).lowest_form(),
286 ] {
287 prop_assert_eq!(
288 lowest_form,
289 Ratio::<Max, Max>::ZERO,
290 "{} != 0", lowest_form,
291 );
292 }
293 }
294 }
295 };
296 }
297
298 macro_rules! lowest_form_ord_iff_ord {
299 ($N: ty, $D:ty, $lowest_form:ident) => {
300 proptest! {
301 #[test]
302 fn $lowest_form(n1: $N, d1: $D, n2: $N, d2: $D) {
303 type R = Ratio<$N, $D>;
304
305 let [(r1, l1), (r2, l2)] = [(n1, d1), (n2, d2)]
306 .map(|(n, d)| {
307 let r = R::new(n, d);
308 (r, r.lowest_form())
309 });
310 prop_assert_eq!(
311 r1.const_cmp(&r2),
312 l1.const_cmp(&l2),
313 "{}, {}, {}, {}",
314 r1, l1, r2, l2,
315 );
316 }
317 }
318 };
319 }
320
321 macro_rules! ord {
322 ($T:ty, $ord:ident) => {
323 proptest! {
324 #[test]
325 fn $ord(common in 1..=<$T>::MAX, a in 1..=<$T>::MAX, b in 1..=<$T>::MAX) {
326 type R = Ratio<$T, $T>;
327
328 if a == b {
329 prop_assert_eq!(
330 R::new(a, common),
331 R::new(b, common),
332 );
333 prop_assert_eq!(
334 R::new(common, a),
335 R::new(common, b),
336 );
337 return Ok(());
338 }
339
340 let (smaller, larger) = if a < b {
341 (a, b)
342 } else {
343 (b, a)
344 };
345 let s = R::new(smaller, common);
346 let l = R::new(larger, common);
347 prop_assert!(s < l, "common d {s}, {l}");
348
349 let s = R::new(common, larger);
350 let l = R::new(common, smaller);
351 prop_assert!(s < l, "common n {s}, {l}");
352 }
353 }
354 };
355 }
356
357 macro_rules! eq_implies_hash_eq {
358 ($N: ty, $D:ty, $eqhash:ident) => {
359 proptest! {
360 #[test]
361 fn $eqhash(n1: $N, d1: $D, n2: $N, d2: $D) {
362 type R = Ratio<$N, $D>;
363
364 let [r1, r2] = [(n1, d1), (n2, d2)]
365 .map(|(n, d)| R::new(n, d));
366 if r1 == r2 {
367 let [mut h1, mut h2] = core::array::from_fn(|_| DefaultHasher::new());
368 for (r, h) in [(r1, &mut h1), (r2, &mut h2)] {
369 r.hash(h);
370 }
371 let [h1, h2] = [h1, h2].map(|h| h.finish());
372 prop_assert_eq!(h1, h2);
373 }
374 }
375 }
376 };
377 }
378
379 ord!(u8, ord_u8);
380 ord!(u16, ord_u16);
381 ord!(u32, ord_u32);
382 ord!(u64, ord_u64);
383
384 zero_eq!(u8, u8, zero_eq_u8_u8);
385 zero_eq!(u8, u16, zero_eq_u8_u16);
386 zero_eq!(u8, u32, zero_eq_u8_u32);
387 zero_eq!(u8, u64, zero_eq_u8_u64);
388
389 zero_eq!(u16, u8, zero_eq_u16_u8);
390 zero_eq!(u16, u16, zero_eq_u16_u16);
391 zero_eq!(u16, u32, zero_eq_u16_u32);
392 zero_eq!(u16, u64, zero_eq_u16_u64);
393
394 zero_eq!(u32, u8, zero_eq_u32_u8);
395 zero_eq!(u32, u16, zero_eq_u32_u16);
396 zero_eq!(u32, u32, zero_eq_u32_u32);
397 zero_eq!(u32, u64, zero_eq_u32_u64);
398
399 zero_eq!(u64, u8, zero_eq_u64_u8);
400 zero_eq!(u64, u16, zero_eq_u64_u16);
401 zero_eq!(u64, u32, zero_eq_u64_u32);
402 zero_eq!(u64, u64, zero_eq_u64_u64);
403
404 lowest_form_ord_iff_ord!(u8, u8, lowest_form_iff_u8_u8);
405 lowest_form_ord_iff_ord!(u8, u16, lowest_form_iff_u8_u16);
406 lowest_form_ord_iff_ord!(u8, u32, lowest_form_iff_u8_u32);
407 lowest_form_ord_iff_ord!(u8, u64, lowest_form_iff_u8_u64);
408
409 lowest_form_ord_iff_ord!(u16, u8, lowest_form_iff_u16_u8);
410 lowest_form_ord_iff_ord!(u16, u16, lowest_form_iff_u16_u16);
411 lowest_form_ord_iff_ord!(u16, u32, lowest_form_iff_u16_u32);
412 lowest_form_ord_iff_ord!(u16, u64, lowest_form_iff_u16_u64);
413
414 lowest_form_ord_iff_ord!(u32, u8, lowest_form_iff_u32_u8);
415 lowest_form_ord_iff_ord!(u32, u16, lowest_form_iff_u32_u16);
416 lowest_form_ord_iff_ord!(u32, u32, lowest_form_iff_u32_u32);
417 lowest_form_ord_iff_ord!(u32, u64, lowest_form_iff_u32_u64);
418
419 lowest_form_ord_iff_ord!(u64, u8, lowest_form_iff_u64_u8);
420 lowest_form_ord_iff_ord!(u64, u16, lowest_form_iff_u64_u16);
421 lowest_form_ord_iff_ord!(u64, u32, lowest_form_iff_u64_u32);
422 lowest_form_ord_iff_ord!(u64, u64, lowest_form_iff_u64_u64);
423
424 eq_implies_hash_eq!(u8, u8, eq_hash_eq_u8_u8);
425 eq_implies_hash_eq!(u8, u16, eq_hash_eq_u8_u16);
426 eq_implies_hash_eq!(u8, u32, eq_hash_eq_u8_u32);
427 eq_implies_hash_eq!(u8, u64, eq_hash_eq_u8_u64);
428
429 eq_implies_hash_eq!(u16, u8, eq_hash_eq_u16_u8);
430 eq_implies_hash_eq!(u16, u16, eq_hash_eq_u16_u16);
431 eq_implies_hash_eq!(u16, u32, eq_hash_eq_u16_u32);
432 eq_implies_hash_eq!(u16, u64, eq_hash_eq_u16_u64);
433
434 eq_implies_hash_eq!(u32, u8, eq_hash_eq_u32_u8);
435 eq_implies_hash_eq!(u32, u16, eq_hash_eq_u32_u16);
436 eq_implies_hash_eq!(u32, u32, eq_hash_eq_u32_u32);
437 eq_implies_hash_eq!(u32, u64, eq_hash_eq_u32_u64);
438
439 eq_implies_hash_eq!(u64, u8, eq_hash_eq_u64_u8);
440 eq_implies_hash_eq!(u64, u16, eq_hash_eq_u64_u16);
441 eq_implies_hash_eq!(u64, u32, eq_hash_eq_u64_u32);
442 eq_implies_hash_eq!(u64, u64, eq_hash_eq_u64_u64);
443}