1#![allow(
7 clippy::cast_possible_truncation,
8 clippy::cast_possible_wrap,
9 clippy::cast_precision_loss,
10 clippy::cast_sign_loss
11)]
12
13use std::cmp::Ordering;
14use std::fmt;
15use std::ops::{Add, Div, Mul, Neg, Sub};
16
17#[derive(Debug, Clone, Copy)]
47pub struct Rational {
48 num: i32,
49 den: i32,
50}
51
52impl PartialEq for Rational {
53 fn eq(&self, other: &Self) -> bool {
54 i64::from(self.num) * i64::from(other.den) == i64::from(other.num) * i64::from(self.den)
57 }
58}
59
60impl Eq for Rational {}
61
62impl std::hash::Hash for Rational {
63 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
64 let reduced = self.reduce();
66 reduced.num.hash(state);
67 reduced.den.hash(state);
68 }
69}
70
71impl Rational {
72 #[must_use]
97 pub const fn new(num: i32, den: i32) -> Self {
98 if den < 0 {
100 Self {
101 num: -num,
102 den: -den,
103 }
104 } else {
105 Self { num, den }
106 }
107 }
108
109 #[must_use]
121 pub const fn zero() -> Self {
122 Self { num: 0, den: 1 }
123 }
124
125 #[must_use]
136 pub const fn one() -> Self {
137 Self { num: 1, den: 1 }
138 }
139
140 #[must_use]
151 #[inline]
152 pub const fn num(&self) -> i32 {
153 self.num
154 }
155
156 #[must_use]
169 #[inline]
170 pub const fn den(&self) -> i32 {
171 self.den
172 }
173
174 #[must_use]
191 #[inline]
192 pub fn as_f64(self) -> f64 {
193 if self.den == 0 {
194 match self.num.cmp(&0) {
195 Ordering::Greater => f64::INFINITY,
196 Ordering::Less => f64::NEG_INFINITY,
197 Ordering::Equal => f64::NAN,
198 }
199 } else {
200 f64::from(self.num) / f64::from(self.den)
201 }
202 }
203
204 #[must_use]
215 #[inline]
216 pub fn as_f32(self) -> f32 {
217 self.as_f64() as f32
218 }
219
220 #[must_use]
239 pub const fn invert(self) -> Self {
240 if self.num < 0 {
242 Self {
243 num: -self.den,
244 den: -self.num,
245 }
246 } else {
247 Self {
248 num: self.den,
249 den: self.num,
250 }
251 }
252 }
253
254 #[must_use]
266 #[inline]
267 pub const fn is_zero(self) -> bool {
268 self.num == 0
269 }
270
271 #[must_use]
283 #[inline]
284 pub const fn is_positive(self) -> bool {
285 self.num > 0 && self.den > 0
286 }
287
288 #[must_use]
300 #[inline]
301 pub const fn is_negative(self) -> bool {
302 self.num < 0 && self.den > 0
303 }
304
305 #[must_use]
316 pub const fn abs(self) -> Self {
317 Self {
318 num: if self.num < 0 { -self.num } else { self.num },
319 den: self.den,
320 }
321 }
322
323 #[must_use]
336 pub fn reduce(self) -> Self {
337 if self.num == 0 {
338 return Self::new(0, 1);
339 }
340 let g = gcd(self.num.unsigned_abs(), self.den.unsigned_abs());
341 Self {
342 num: self.num / g as i32,
343 den: self.den / g as i32,
344 }
345 }
346}
347
348fn gcd(mut a: u32, mut b: u32) -> u32 {
350 while b != 0 {
351 let temp = b;
352 b = a % b;
353 a = temp;
354 }
355 a
356}
357
358impl Default for Rational {
359 fn default() -> Self {
361 Self::one()
362 }
363}
364
365impl fmt::Display for Rational {
366 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
367 write!(f, "{}/{}", self.num, self.den)
368 }
369}
370
371impl From<i32> for Rational {
372 fn from(n: i32) -> Self {
373 Self::new(n, 1)
374 }
375}
376
377impl From<(i32, i32)> for Rational {
378 fn from((num, den): (i32, i32)) -> Self {
379 Self::new(num, den)
380 }
381}
382
383impl Add for Rational {
386 type Output = Self;
387
388 fn add(self, rhs: Self) -> Self::Output {
389 let num =
391 i64::from(self.num) * i64::from(rhs.den) + i64::from(rhs.num) * i64::from(self.den);
392 let den = i64::from(self.den) * i64::from(rhs.den);
393
394 let g = gcd(num.unsigned_abs() as u32, den.unsigned_abs() as u32);
396 Self::new((num / i64::from(g)) as i32, (den / i64::from(g)) as i32)
397 }
398}
399
400impl Sub for Rational {
401 type Output = Self;
402
403 fn sub(self, rhs: Self) -> Self::Output {
404 let num =
406 i64::from(self.num) * i64::from(rhs.den) - i64::from(rhs.num) * i64::from(self.den);
407 let den = i64::from(self.den) * i64::from(rhs.den);
408
409 let g = gcd(num.unsigned_abs() as u32, den.unsigned_abs() as u32);
410 Self::new((num / i64::from(g)) as i32, (den / i64::from(g)) as i32)
411 }
412}
413
414impl Mul for Rational {
415 type Output = Self;
416
417 fn mul(self, rhs: Self) -> Self::Output {
418 let num = i64::from(self.num) * i64::from(rhs.num);
420 let den = i64::from(self.den) * i64::from(rhs.den);
421
422 let g = gcd(num.unsigned_abs() as u32, den.unsigned_abs() as u32);
423 Self::new((num / i64::from(g)) as i32, (den / i64::from(g)) as i32)
424 }
425}
426
427impl Div for Rational {
428 type Output = Self;
429
430 #[allow(clippy::suspicious_arithmetic_impl)]
431 fn div(self, rhs: Self) -> Self::Output {
432 self * rhs.invert()
435 }
436}
437
438impl Mul<i32> for Rational {
439 type Output = Self;
440
441 fn mul(self, rhs: i32) -> Self::Output {
442 let num = i64::from(self.num) * i64::from(rhs);
443 let g = gcd(num.unsigned_abs() as u32, self.den.unsigned_abs());
444 Self::new((num / i64::from(g)) as i32, self.den / g as i32)
445 }
446}
447
448impl Div<i32> for Rational {
449 type Output = Self;
450
451 fn div(self, rhs: i32) -> Self::Output {
452 let den = i64::from(self.den) * i64::from(rhs);
453 let g = gcd(self.num.unsigned_abs(), den.unsigned_abs() as u32);
454 Self::new(self.num / g as i32, (den / i64::from(g)) as i32)
455 }
456}
457
458impl Neg for Rational {
459 type Output = Self;
460
461 fn neg(self) -> Self::Output {
462 Self::new(-self.num, self.den)
463 }
464}
465
466impl PartialOrd for Rational {
467 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
468 Some(self.cmp(other))
469 }
470}
471
472impl Ord for Rational {
473 fn cmp(&self, other: &Self) -> Ordering {
474 let left = i64::from(self.num) * i64::from(other.den);
476 let right = i64::from(other.num) * i64::from(self.den);
477 left.cmp(&right)
478 }
479}
480
481#[cfg(test)]
482#[allow(
483 clippy::unwrap_used,
484 clippy::float_cmp,
485 clippy::similar_names,
486 clippy::redundant_closure_for_method_calls
487)]
488mod tests {
489 use super::*;
490
491 fn approx_eq(a: f64, b: f64) -> bool {
493 (a - b).abs() < 1e-9
494 }
495
496 mod rational_tests {
497 use super::*;
498
499 #[test]
500 fn test_new() {
501 let r = Rational::new(1, 2);
502 assert_eq!(r.num(), 1);
503 assert_eq!(r.den(), 2);
504 }
505
506 #[test]
507 fn test_new_negative_denominator() {
508 let r = Rational::new(1, -2);
510 assert_eq!(r.num(), -1);
511 assert_eq!(r.den(), 2);
512
513 let r = Rational::new(-1, -2);
514 assert_eq!(r.num(), 1);
515 assert_eq!(r.den(), 2);
516 }
517
518 #[test]
519 fn test_zero_and_one() {
520 let zero = Rational::zero();
521 assert!(zero.is_zero());
522 assert!(approx_eq(zero.as_f64(), 0.0));
523
524 let one = Rational::one();
525 assert!(approx_eq(one.as_f64(), 1.0));
526 assert!(!one.is_zero());
527 }
528
529 #[test]
530 fn test_as_f64() {
531 assert!(approx_eq(Rational::new(1, 2).as_f64(), 0.5));
532 assert!(approx_eq(Rational::new(1, 4).as_f64(), 0.25));
533 assert!((Rational::new(1, 3).as_f64() - 0.333_333).abs() < 0.001);
534 assert!(approx_eq(Rational::new(-1, 2).as_f64(), -0.5));
535 }
536
537 #[test]
538 fn test_as_f64_division_by_zero() {
539 assert!(Rational::new(1, 0).as_f64().is_infinite());
540 assert!(Rational::new(1, 0).as_f64().is_sign_positive());
541 assert!(Rational::new(-1, 0).as_f64().is_infinite());
542 assert!(Rational::new(-1, 0).as_f64().is_sign_negative());
543 assert!(Rational::new(0, 0).as_f64().is_nan());
544 }
545
546 #[test]
547 fn test_as_f32() {
548 assert_eq!(Rational::new(1, 2).as_f32(), 0.5);
549 }
550
551 #[test]
552 fn test_invert() {
553 let r = Rational::new(3, 4);
554 let inv = r.invert();
555 assert_eq!(inv.num(), 4);
556 assert_eq!(inv.den(), 3);
557
558 let r = Rational::new(-3, 4);
560 let inv = r.invert();
561 assert_eq!(inv.num(), -4);
562 assert_eq!(inv.den(), 3);
563 }
564
565 #[test]
566 fn test_is_positive_negative() {
567 assert!(Rational::new(1, 2).is_positive());
568 assert!(!Rational::new(-1, 2).is_positive());
569 assert!(!Rational::new(0, 1).is_positive());
570
571 assert!(Rational::new(-1, 2).is_negative());
572 assert!(!Rational::new(1, 2).is_negative());
573 assert!(!Rational::new(0, 1).is_negative());
574 }
575
576 #[test]
577 fn test_abs() {
578 assert_eq!(Rational::new(-3, 4).abs(), Rational::new(3, 4));
579 assert_eq!(Rational::new(3, 4).abs(), Rational::new(3, 4));
580 assert_eq!(Rational::new(0, 4).abs(), Rational::new(0, 4));
581 }
582
583 #[test]
584 fn test_reduce() {
585 let r = Rational::new(4, 8);
586 let reduced = r.reduce();
587 assert_eq!(reduced.num(), 1);
588 assert_eq!(reduced.den(), 2);
589
590 let r = Rational::new(6, 9);
591 let reduced = r.reduce();
592 assert_eq!(reduced.num(), 2);
593 assert_eq!(reduced.den(), 3);
594
595 let r = Rational::new(0, 5);
596 let reduced = r.reduce();
597 assert_eq!(reduced.num(), 0);
598 assert_eq!(reduced.den(), 1);
599 }
600
601 #[test]
602 fn test_add() {
603 let a = Rational::new(1, 2);
604 let b = Rational::new(1, 4);
605 let result = a + b;
606 assert!((result.as_f64() - 0.75).abs() < 0.0001);
607 }
608
609 #[test]
610 fn test_sub() {
611 let a = Rational::new(1, 2);
612 let b = Rational::new(1, 4);
613 let result = a - b;
614 assert!((result.as_f64() - 0.25).abs() < 0.0001);
615 }
616
617 #[test]
618 fn test_mul() {
619 let a = Rational::new(1, 2);
620 let b = Rational::new(2, 3);
621 let result = a * b;
622 assert!((result.as_f64() - (1.0 / 3.0)).abs() < 0.0001);
623 }
624
625 #[test]
626 fn test_div() {
627 let a = Rational::new(1, 2);
628 let b = Rational::new(2, 3);
629 let result = a / b;
630 assert!((result.as_f64() - 0.75).abs() < 0.0001);
631 }
632
633 #[test]
634 fn test_mul_i32() {
635 let r = Rational::new(1, 4);
636 let result = r * 2;
637 assert!((result.as_f64() - 0.5).abs() < 0.0001);
638 }
639
640 #[test]
641 fn test_div_i32() {
642 let r = Rational::new(1, 2);
643 let result = r / 2;
644 assert!((result.as_f64() - 0.25).abs() < 0.0001);
645 }
646
647 #[test]
648 fn test_neg() {
649 let r = Rational::new(1, 2);
650 let neg = -r;
651 assert_eq!(neg.num(), -1);
652 assert_eq!(neg.den(), 2);
653 }
654
655 #[test]
656 fn test_ord() {
657 let a = Rational::new(1, 2);
658 let b = Rational::new(1, 3);
659 let c = Rational::new(2, 4);
660
661 assert!(a > b);
662 assert!(b < a);
663 assert_eq!(a, c);
664 assert!(a >= c);
665 assert!(a <= c);
666 }
667
668 #[test]
669 fn test_from_i32() {
670 let r: Rational = 5.into();
671 assert_eq!(r.num(), 5);
672 assert_eq!(r.den(), 1);
673 }
674
675 #[test]
676 fn test_from_tuple() {
677 let r: Rational = (3, 4).into();
678 assert_eq!(r.num(), 3);
679 assert_eq!(r.den(), 4);
680 }
681
682 #[test]
683 fn test_display() {
684 assert_eq!(format!("{}", Rational::new(1, 2)), "1/2");
685 assert_eq!(format!("{}", Rational::new(-3, 4)), "-3/4");
686 }
687
688 #[test]
689 fn test_default() {
690 assert_eq!(Rational::default(), Rational::one());
691 }
692
693 #[test]
694 fn test_common_frame_rates() {
695 let fps = Rational::new(24000, 1001);
697 assert!((fps.as_f64() - 23.976).abs() < 0.001);
698
699 let fps = Rational::new(30000, 1001);
701 assert!((fps.as_f64() - 29.97).abs() < 0.01);
702
703 let fps = Rational::new(60000, 1001);
705 assert!((fps.as_f64() - 59.94).abs() < 0.01);
706 }
707 }
708
709 #[test]
712 fn test_gcd() {
713 assert_eq!(gcd(12, 8), 4);
714 assert_eq!(gcd(17, 13), 1);
715 assert_eq!(gcd(100, 25), 25);
716 assert_eq!(gcd(0, 5), 5);
717 assert_eq!(gcd(5, 0), 5);
718 }
719}