1use zerocopy::{FromBytes, Immutable, IntoBytes};
6use zr::static_assert;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum Exact {
10 No,
11 Yes,
12}
13
14pub struct Round;
21impl Round {
22 pub const DOWN: u8 = 0;
23 pub const UP: u8 = 1;
24 pub const TOWARDS_ZERO: u8 = 2;
25 pub const AWAY_FROM_ZERO: u8 = 3;
26}
27
28#[repr(C)]
29#[derive(Debug, Clone, Copy, PartialEq, Eq, FromBytes, IntoBytes, Immutable)]
30pub struct Ratio {
31 numerator: u32,
32 denominator: u32,
33}
34
35static_assert!(core::mem::size_of::<Ratio>() == 8);
36static_assert!(core::mem::align_of::<Ratio>() == 4);
37
38impl Default for Ratio {
39 fn default() -> Self {
40 Self::const_default()
41 }
42}
43
44impl Ratio {
45 pub const OVERFLOW: i64 = i64::MAX;
46 pub const UNDERFLOW: i64 = i64::MIN;
47
48 pub const fn const_default() -> Self {
49 Ratio { numerator: 1, denominator: 1 }
50 }
51
52 pub const fn new(numerator: u32, denominator: u32) -> Self {
53 debug_assert!(denominator != 0);
54 Ratio { numerator, denominator }
55 }
56
57 pub const fn numerator(&self) -> u32 {
58 self.numerator
59 }
60
61 pub const fn denominator(&self) -> u32 {
62 self.denominator
63 }
64
65 pub const fn invertible(&self) -> bool {
66 self.numerator != 0
67 }
68
69 pub const fn inverse(&self) -> Self {
70 debug_assert!(self.invertible());
71 Ratio { numerator: self.denominator, denominator: self.numerator }
72 }
73
74 pub fn reduce_u32(numerator: &mut u32, denominator: &mut u32) {
76 assert!(*denominator != 0);
77 if *numerator == 0 {
78 *denominator = 1;
79 return;
80 }
81 let gcd = binary_gcd(*numerator as u64, *denominator as u64) as u32;
82 *numerator /= gcd;
83 *denominator /= gcd;
84 }
85
86 pub fn reduce_u64(numerator: &mut u64, denominator: &mut u64) {
88 assert!(*denominator != 0);
89 if *numerator == 0 {
90 *denominator = 1;
91 return;
92 }
93 let gcd = binary_gcd(*numerator, *denominator);
94 *numerator /= gcd;
95 *denominator /= gcd;
96 }
97
98 pub fn reduce(&mut self) {
100 Self::reduce_u32(&mut self.numerator, &mut self.denominator);
101 }
102
103 pub fn product_raw(
108 a_numerator: u32,
109 a_denominator: u32,
110 b_numerator: u32,
111 b_denominator: u32,
112 exact: Exact,
113 ) -> (u32, u32) {
114 let mut numerator = a_numerator as u64 * b_numerator as u64;
115 let mut denominator = a_denominator as u64 * b_denominator as u64;
116
117 Self::reduce_u64(&mut numerator, &mut denominator);
118
119 if numerator > u32::MAX as u64 || denominator > u32::MAX as u64 {
120 assert!(exact == Exact::No, "Precision loss in exact Ratio::product");
121
122 for i in 1..=32 {
137 let rounded_numerator = (numerator + (1u64 << (i - 1))) >> i;
141 let rounded_denominator = (denominator + (1u64 << (i - 1))) >> i;
142
143 if rounded_denominator == 0 {
144 return (u32::MAX, 1);
147 }
148
149 if rounded_numerator == 0 {
150 return (0, 1);
152 }
153
154 let mut rn = rounded_numerator;
155 let mut rd = rounded_denominator;
156 Self::reduce_u64(&mut rn, &mut rd);
157 if rn <= u32::MAX as u64 && rd <= u32::MAX as u64 {
158 return (rn as u32, rd as u32);
159 }
160 }
161 return (numerator as u32, denominator as u32);
163 }
164
165 (numerator as u32, denominator as u32)
166 }
167
168 pub fn product(a: Ratio, b: Ratio, exact: Exact) -> Ratio {
169 let (n, d) =
170 Self::product_raw(a.numerator, a.denominator, b.numerator, b.denominator, exact);
171 Ratio { numerator: n, denominator: d }
172 }
173
174 pub fn scale_with_round<const ROUND: u8>(value: i64, numerator: u32, denominator: u32) -> i64 {
179 assert!(denominator != 0);
180
181 if value >= 0 {
182 const LIMIT: u64 = i64::MAX as u64; let value = value as u64;
184 let scaled = match ROUND {
185 Round::UP | Round::AWAY_FROM_ZERO => {
186 scale_unsigned::<true, LIMIT>(value, numerator, denominator)
187 }
188 _ => scale_unsigned::<false, LIMIT>(value, numerator, denominator),
189 };
190 scaled as i64
191 } else {
192 const LIMIT: u64 = 0x8000000000000000; let value = value.unsigned_abs();
208 let scaled = match ROUND {
209 Round::DOWN | Round::AWAY_FROM_ZERO => {
210 scale_unsigned::<true, LIMIT>(value, numerator, denominator)
211 }
212 _ => scale_unsigned::<false, LIMIT>(value, numerator, denominator),
213 };
214 if scaled == LIMIT { i64::MIN } else { -(scaled as i64) }
215 }
216 }
217
218 pub fn scale<const ROUND: u8>(&self, value: i64) -> i64 {
223 Self::scale_with_round::<ROUND>(value, self.numerator, self.denominator)
224 }
225}
226
227fn binary_gcd(mut a: u64, mut b: u64) -> u64 {
229 debug_assert!(a != 0 && b != 0);
230
231 let mut twos = 0;
233 while ((a | b) & 1) == 0 {
234 a >>= 1;
235 b >>= 1;
236 twos += 1;
237 }
238
239 while (a & 1) == 0 {
242 a >>= 1;
243 }
244
245 loop {
246 while (b & 1) == 0 {
249 b >>= 1;
250 }
251
252 if a > b {
254 core::mem::swap(&mut a, &mut b);
255 }
256
257 b -= a;
258 if b == 0 {
259 break;
260 }
261 }
262
263 a << twos
265}
266
267fn scale_unsigned<const ROUND_UP: bool, const LIMIT: u64>(
270 value: u64,
271 numerator: u32,
272 denominator: u32,
273) -> u64 {
274 let numerator = numerator as u64;
275 let denominator = denominator as u64;
276 const LOW_32_BITS: u64 = u32::MAX as u64;
277
278 let low_product = numerator * (value & LOW_32_BITS);
282 let high = numerator * (value >> 32) + (low_product >> 32);
283 let mut low = low_product & LOW_32_BITS;
284
285 let high_q = high / denominator;
290 let high_r = high % denominator;
291
292 if high_q > LIMIT >> 32 {
296 return LIMIT;
297 }
298
299 low |= high_r << 32;
302
303 let low_q = low / denominator;
304 let low_r = low % denominator;
305 let result = (high_q << 32) | low_q;
306 if result >= LIMIT {
307 return LIMIT;
308 }
309
310 if ROUND_UP && low_r != 0 {
314 return result + 1;
315 }
316
317 result
318}
319
320impl core::ops::Mul for Ratio {
323 type Output = Self;
324 fn mul(self, rhs: Self) -> Self::Output {
325 Ratio::product(self, rhs, Exact::Yes)
326 }
327}
328
329#[allow(clippy::suspicious_arithmetic_impl)]
331impl core::ops::Div for Ratio {
332 type Output = Self;
333 fn div(self, rhs: Self) -> Self::Output {
334 self * rhs.inverse()
335 }
336}
337
338impl core::ops::Mul<i64> for Ratio {
339 type Output = i64;
340 fn mul(self, rhs: i64) -> Self::Output {
341 self.scale::<{ Round::DOWN }>(rhs)
342 }
343}
344
345impl core::ops::Mul<Ratio> for i64 {
346 type Output = i64;
347 fn mul(self, rhs: Ratio) -> Self::Output {
348 rhs.scale::<{ Round::DOWN }>(self)
349 }
350}
351
352#[allow(clippy::suspicious_arithmetic_impl)]
354impl core::ops::Div<Ratio> for i64 {
355 type Output = i64;
356 fn div(self, rhs: Ratio) -> Self::Output {
357 rhs.inverse().scale::<{ Round::DOWN }>(self)
358 }
359}
360
361#[cfg(test)]
362mod tests {
363 use super::*;
364
365 #[test]
366 fn test_construction() {
367 let valid_vectors = [(0, 1), (1, 1), (23, 41)];
368 for &(n, d) in &valid_vectors {
369 let r = Ratio::new(n, d);
370 assert_eq!(r.numerator(), n);
371 assert_eq!(r.denominator(), d);
372 }
373
374 let r = Ratio::default();
376 assert_eq!(r.numerator(), 1);
377 assert_eq!(r.denominator(), 1);
378
379 let r = Ratio::new(9, 21);
381 assert_eq!(r.numerator(), 9);
382 assert_eq!(r.denominator(), 21);
383 }
384
385 #[test]
386 fn test_reduction_32() {
387 let mut vectors = [
388 (1, 1, 1, 1),
389 (10, 10, 1, 1),
390 (10, 2, 5, 1),
391 (0, 1, 0, 1),
392 (0, 500, 0, 1),
393 (48000, 44100, 160, 147),
394 (44100, 48000, 147, 160),
395 (1000007, 1000000, 1000007, 1000000),
396 ];
397
398 for v in &mut vectors {
399 let mut n = v.0;
400 let mut d = v.1;
401 Ratio::reduce_u32(&mut n, &mut d);
402 assert_eq!((n, d), (v.2, v.3));
403
404 let mut r = Ratio::new(v.0, v.1);
405 r.reduce();
406 assert_eq!((r.numerator(), r.denominator()), (v.2, v.3));
407 }
408 }
409
410 #[test]
411 fn test_reduction_64() {
412 let mut vectors = [
413 (1, 1, 1, 1),
414 (10, 10, 1, 1),
415 (10, 2, 5, 1),
416 (0, 1, 0, 1),
417 (0, 500, 0, 1),
418 (48000, 44100, 160, 147),
419 (44100, 48000, 147, 160),
420 (1000007, 1000000, 1000007, 1000000),
421 (48000336000, 44100000000, 1000007, 918750),
422 ];
423
424 for v in &mut vectors {
425 let mut n = v.0;
426 let mut d = v.1;
427 Ratio::reduce_u64(&mut n, &mut d);
428 assert_eq!((n, d), (v.2, v.3));
429 }
430 }
431
432 #[test]
433 fn test_product() {
434 struct TestVector {
435 a_n: u32,
436 a_d: u32,
437 b_n: u32,
438 b_d: u32,
439 expected_n: u32,
440 expected_d: u32,
441 exact: Exact,
442 }
443
444 let test_vectors = [
445 TestVector {
446 a_n: 1,
447 a_d: 1,
448 b_n: 1,
449 b_d: 1,
450 expected_n: 1,
451 expected_d: 1,
452 exact: Exact::Yes,
453 },
454 TestVector {
455 a_n: 0,
456 a_d: 1,
457 b_n: 1,
458 b_d: 1,
459 expected_n: 0,
460 expected_d: 1,
461 exact: Exact::Yes,
462 },
463 TestVector {
464 a_n: 0,
465 a_d: 500,
466 b_n: 1,
467 b_d: 1,
468 expected_n: 0,
469 expected_d: 1,
470 exact: Exact::Yes,
471 },
472 TestVector {
473 a_n: 3,
474 a_d: 4,
475 b_n: 5,
476 b_d: 9,
477 expected_n: 5,
478 expected_d: 12,
479 exact: Exact::Yes,
480 },
481 TestVector {
482 a_n: 48000,
483 a_d: 44100,
484 b_n: 1000007,
485 b_d: 1000000,
486 expected_n: 1000007,
487 expected_d: 918750,
488 exact: Exact::Yes,
489 },
490 TestVector {
491 a_n: 3465653567,
492 a_d: 2327655023,
493 b_n: 1291540343,
494 b_d: 3698423317,
495 expected_n: 317609835,
496 expected_d: 610852072,
497 exact: Exact::No,
498 },
499 TestVector {
500 a_n: 0xFFFFFFFF,
501 a_d: 1,
502 b_n: 0xFFFFFFFF,
503 b_d: 1,
504 expected_n: 0xFFFFFFFF,
505 expected_d: 1,
506 exact: Exact::No,
507 },
508 TestVector {
509 a_n: 1,
510 a_d: 0xFFFFFFFF,
511 b_n: 1,
512 b_d: 0xFFFFFFFF,
513 expected_n: 0,
514 expected_d: 1,
515 exact: Exact::No,
516 },
517 ];
518
519 for v in &test_vectors {
520 let a = Ratio::new(v.a_n, v.a_d);
521 let b = Ratio::new(v.b_n, v.b_d);
522
523 let res = Ratio::product(a, b, v.exact);
524 assert_eq!(
525 (res.numerator(), res.denominator()),
526 (v.expected_n, v.expected_d),
527 "Expected {}/{} * {}/{} to produce {}/{}; got {}/{} instead (static)",
528 v.a_n,
529 v.a_d,
530 v.b_n,
531 v.b_d,
532 v.expected_n,
533 v.expected_d,
534 res.numerator(),
535 res.denominator()
536 );
537
538 let res = Ratio::product(b, a, v.exact);
539 assert_eq!(
540 (res.numerator(), res.denominator()),
541 (v.expected_n, v.expected_d),
542 "Expected {}/{} * {}/{} to produce {}/{}; got {}/{} instead (commutative static)",
543 v.b_n,
544 v.b_d,
545 v.a_n,
546 v.a_d,
547 v.expected_n,
548 v.expected_d,
549 res.numerator(),
550 res.denominator()
551 );
552
553 if v.exact == Exact::Yes {
554 let res = a * b;
555 assert_eq!((res.numerator(), res.denominator()), (v.expected_n, v.expected_d));
556
557 let res = b * a;
558 assert_eq!((res.numerator(), res.denominator()), (v.expected_n, v.expected_d));
559
560 if b.invertible() {
561 let res = a / b.inverse();
562 assert_eq!((res.numerator(), res.denominator()), (v.expected_n, v.expected_d));
563 }
564
565 if a.invertible() {
566 let res = b / a.inverse();
567 assert_eq!((res.numerator(), res.denominator()), (v.expected_n, v.expected_d));
568 }
569 }
570 }
571 }
572
573 #[test]
574 fn test_product_raw() {
575 struct TestVector {
576 a_n: u32,
577 a_d: u32,
578 b_n: u32,
579 b_d: u32,
580 expected_n: u32,
581 expected_d: u32,
582 exact: Exact,
583 }
584
585 let test_vectors = [
586 TestVector {
587 a_n: 1,
588 a_d: 1,
589 b_n: 1,
590 b_d: 1,
591 expected_n: 1,
592 expected_d: 1,
593 exact: Exact::Yes,
594 },
595 TestVector {
596 a_n: 0,
597 a_d: 1,
598 b_n: 1,
599 b_d: 1,
600 expected_n: 0,
601 expected_d: 1,
602 exact: Exact::Yes,
603 },
604 TestVector {
605 a_n: 0,
606 a_d: 500,
607 b_n: 1,
608 b_d: 1,
609 expected_n: 0,
610 expected_d: 1,
611 exact: Exact::Yes,
612 },
613 TestVector {
614 a_n: 3,
615 a_d: 4,
616 b_n: 5,
617 b_d: 9,
618 expected_n: 5,
619 expected_d: 12,
620 exact: Exact::Yes,
621 },
622 TestVector {
623 a_n: 48000,
624 a_d: 44100,
625 b_n: 1000007,
626 b_d: 1000000,
627 expected_n: 1000007,
628 expected_d: 918750,
629 exact: Exact::Yes,
630 },
631 TestVector {
632 a_n: 3465653567,
633 a_d: 2327655023,
634 b_n: 1291540343,
635 b_d: 3698423317,
636 expected_n: 317609835,
637 expected_d: 610852072,
638 exact: Exact::No,
639 },
640 TestVector {
641 a_n: 0xFFFFFFFF,
642 a_d: 1,
643 b_n: 0xFFFFFFFF,
644 b_d: 1,
645 expected_n: 0xFFFFFFFF,
646 expected_d: 1,
647 exact: Exact::No,
648 },
649 TestVector {
650 a_n: 1,
651 a_d: 0xFFFFFFFF,
652 b_n: 1,
653 b_d: 0xFFFFFFFF,
654 expected_n: 0,
655 expected_d: 1,
656 exact: Exact::No,
657 },
658 ];
659
660 for v in &test_vectors {
661 let res = Ratio::product_raw(v.a_n, v.a_d, v.b_n, v.b_d, v.exact);
662 assert_eq!(
663 res,
664 (v.expected_n, v.expected_d),
665 "Expected {}/{} * {}/{} to produce {}/{}; got {}/{} instead",
666 v.a_n,
667 v.a_d,
668 v.b_n,
669 v.b_d,
670 v.expected_n,
671 v.expected_d,
672 res.0,
673 res.1
674 );
675 }
676 }
677
678 fn test_scale_helper<const ROUND: u8>() {
679 struct TestVector {
680 val: i64,
681 n: u32,
682 d: u32,
683 expected: i64,
684 fractional_result: bool,
685 }
686
687 let test_vectors = [
688 TestVector { val: 0, n: 0, d: 1, expected: 0, fractional_result: false },
689 TestVector { val: 1234567890, n: 0, d: 1, expected: 0, fractional_result: false },
690 TestVector { val: 0, n: 1, d: 1, expected: 0, fractional_result: false },
691 TestVector {
692 val: 1234567890,
693 n: 1,
694 d: 1,
695 expected: 1234567890,
696 fractional_result: false,
697 },
698 TestVector { val: 198, n: 48000, d: 44100, expected: 215, fractional_result: true },
699 TestVector { val: -198, n: 48000, d: 44100, expected: -216, fractional_result: true },
700 TestVector {
701 val: 49 * 198,
702 n: 48000,
703 d: 44100,
704 expected: 10560,
705 fractional_result: false,
706 },
707 TestVector {
708 val: -(49 * 198),
709 n: 48000,
710 d: 44100,
711 expected: -10560,
712 fractional_result: false,
713 },
714 TestVector {
715 val: (49 * 198) + 1,
716 n: 48000,
717 d: 44100,
718 expected: 10561,
719 fractional_result: true,
720 },
721 TestVector {
722 val: -((49 * 198) + 1),
723 n: 48000,
724 d: 44100,
725 expected: -10562,
726 fractional_result: true,
727 },
728 TestVector {
729 val: 0x1517ffffeae80,
730 n: 0xbebc200,
731 d: 0x33333333,
732 expected: 0x4e94914f0000,
733 fractional_result: false,
734 },
735 TestVector {
736 val: -0x1517ffffeae80,
737 n: 0xbebc200,
738 d: 0x33333333,
739 expected: -0x4e94914f0000,
740 fractional_result: false,
741 },
742 TestVector {
743 val: i64::MAX,
744 n: 1000001,
745 d: 1000000,
746 expected: Ratio::OVERFLOW,
747 fractional_result: false,
748 },
749 TestVector {
750 val: i64::MIN,
751 n: 1000001,
752 d: 1000000,
753 expected: Ratio::UNDERFLOW,
754 fractional_result: false,
755 },
756 TestVector {
757 val: -0x2000000000000001,
758 n: 4,
759 d: 1,
760 expected: Ratio::UNDERFLOW,
761 fractional_result: false,
762 },
763 ];
764
765 for v in &test_vectors {
766 let res_static = Ratio::scale_with_round::<ROUND>(v.val, v.n, v.d);
767 let r = Ratio::new(v.n, v.d);
768 let res_inst = r.scale::<ROUND>(v.val);
769
770 let adjusted_expected = if !v.fractional_result || ROUND == Round::DOWN {
771 v.expected
772 } else if v.val >= 0 {
773 if ROUND == Round::TOWARDS_ZERO { v.expected } else { v.expected + 1 }
774 } else {
775 if ROUND == Round::AWAY_FROM_ZERO { v.expected } else { v.expected + 1 }
776 };
777
778 assert_eq!(
779 res_static, adjusted_expected,
780 "Static: Expected {} * {}/{} to produce {}; got {}",
781 v.val, v.n, v.d, adjusted_expected, res_static
782 );
783 assert_eq!(
784 res_inst, adjusted_expected,
785 "Instanced: Expected {} * {}/{} to produce {}; got {}",
786 v.val, v.n, v.d, adjusted_expected, res_inst
787 );
788
789 if ROUND == Round::DOWN {
790 let res_op1 = r * v.val;
791 let res_op2 = v.val * r;
792 assert_eq!(res_op1, adjusted_expected);
793 assert_eq!(res_op2, adjusted_expected);
794
795 if r.invertible() {
796 let res_op3 = v.val / r.inverse();
797 assert_eq!(res_op3, adjusted_expected);
798 }
799 }
800 }
801 }
802
803 #[test]
804 fn test_scale_round_down() {
805 test_scale_helper::<{ Round::DOWN }>();
806 }
807 #[test]
808 fn test_scale_round_up() {
809 test_scale_helper::<{ Round::UP }>();
810 }
811 #[test]
812 fn test_scale_round_towards_zero() {
813 test_scale_helper::<{ Round::TOWARDS_ZERO }>();
814 }
815 #[test]
816 fn test_scale_round_away_from_zero() {
817 test_scale_helper::<{ Round::AWAY_FROM_ZERO }>();
818 }
819
820 #[test]
821 fn test_inverse() {
822 let test_vectors = [(1, 1), (123456, 987654)];
823 for &(n, d) in &test_vectors {
824 let r = Ratio::new(n, d);
825 let inv = r.inverse();
826 assert_eq!(inv.numerator(), d);
827 assert_eq!(inv.denominator(), n);
828 }
829
830 let r = Ratio::new(0, 1);
831 assert!(!r.invertible());
832 }
833
834 const POSITIVE_LIMIT: u64 = i64::MAX as u64; const NEGATIVE_LIMIT: u64 = 0x8000000000000000; macro_rules! check_scale_unsigned {
844 ($value:expr, $numerator:expr, $denominator:expr, $round_up:expr, $limit:expr,
845 $expected:expr $(,)?) => {{
846 let value: u64 = $value;
847 let numerator: u32 = $numerator;
848 let denominator: u32 = $denominator;
849 let expected: u64 = $expected;
850 let res = scale_unsigned::<{ $round_up }, { $limit }>(value, numerator, denominator);
851 assert_eq!(
852 res, expected,
853 "Expected scale_unsigned::<{}, {:#x}>({:#x}, {}, {}) to produce {:#x}; got {:#x}",
854 $round_up, $limit, value, numerator, denominator, expected, res
855 );
856 }};
857 }
858
859 #[test]
862 fn test_scale_unsigned_early_out() {
863 check_scale_unsigned!(0x8000000000000000, 1, 1, false, POSITIVE_LIMIT, POSITIVE_LIMIT);
866
867 check_scale_unsigned!(0x8000000000000000, 1, 1, false, NEGATIVE_LIMIT, 0x8000000000000000);
871
872 check_scale_unsigned!(0x7fffffff00000000, 1, 1, false, POSITIVE_LIMIT, 0x7fffffff00000000);
875
876 check_scale_unsigned!(u64::MAX, u32::MAX, 1, true, POSITIVE_LIMIT, POSITIVE_LIMIT);
880 check_scale_unsigned!(u64::MAX, u32::MAX, u32::MAX, false, u64::MAX, u64::MAX);
881 }
882
883 #[test]
887 fn test_scale_unsigned_result_saturation() {
888 check_scale_unsigned!(0x8000000000000001, 1, 1, false, NEGATIVE_LIMIT, NEGATIVE_LIMIT);
890
891 check_scale_unsigned!(0x4000000000000000, 2, 1, false, NEGATIVE_LIMIT, NEGATIVE_LIMIT);
894
895 check_scale_unsigned!(POSITIVE_LIMIT, 1, 1, true, POSITIVE_LIMIT, POSITIVE_LIMIT);
897
898 check_scale_unsigned!(1000, 3, 1, false, 100, 100);
901 }
902
903 #[test]
906 fn test_scale_unsigned_round_up_at_limit() {
907 check_scale_unsigned!(u64::MAX, 1, 2, true, NEGATIVE_LIMIT, NEGATIVE_LIMIT);
910
911 check_scale_unsigned!(u64::MAX, 1, 2, false, NEGATIVE_LIMIT, NEGATIVE_LIMIT - 1);
913
914 check_scale_unsigned!(0xfffffffffffffffd, 1, 2, true, POSITIVE_LIMIT, POSITIVE_LIMIT);
917
918 check_scale_unsigned!(0xfffffffffffffffb, 1, 2, true, POSITIVE_LIMIT, POSITIVE_LIMIT - 1);
921
922 check_scale_unsigned!(0xfffffffffffffffc, 1, 2, true, POSITIVE_LIMIT, POSITIVE_LIMIT - 1);
924
925 check_scale_unsigned!(198, 48000, 44100, true, POSITIVE_LIMIT, 216);
927 check_scale_unsigned!(198, 48000, 44100, false, POSITIVE_LIMIT, 215);
928 }
929
930 fn scale_unsigned_reference(
933 value: u64,
934 numerator: u32,
935 denominator: u32,
936 round_up: bool,
937 limit: u64,
938 ) -> u64 {
939 let prod = (value as u128) * (numerator as u128);
940 let q = prod / (denominator as u128);
941 let r = prod % (denominator as u128);
942
943 if q >= (limit as u128) {
944 return limit;
945 }
946
947 let mut result = q as u64;
948 if round_up && r != 0 {
949 result += 1;
950 if result >= limit {
951 return limit;
952 }
953 }
954
955 result
956 }
957
958 fn check_against_reference<const ROUND_UP: bool, const LIMIT: u64>() {
962 let values = [
963 0,
964 1,
965 2,
966 0xffff_ffff,
967 0x1_0000_0000,
968 0x1_0000_0001,
969 0x7fff_ffff_ffff_fffe,
970 POSITIVE_LIMIT,
971 NEGATIVE_LIMIT,
972 0x8000_0000_0000_0001,
973 0xffff_ffff_ffff_fffd,
974 u64::MAX,
975 0x1517ffffeae80,
976 1234567890,
977 ];
978 let numerators = [0, 1, 2, 3, 48000, 44100, 1000001, 0x7fff_ffff, u32::MAX];
979 let denominators = [1, 2, 3, 7, 44100, 1000000, 0x7fff_ffff, u32::MAX];
980
981 for &value in &values {
982 for &numerator in &numerators {
983 for &denominator in &denominators {
984 let res = scale_unsigned::<ROUND_UP, LIMIT>(value, numerator, denominator);
985 let expected =
986 scale_unsigned_reference(value, numerator, denominator, ROUND_UP, LIMIT);
987 assert_eq!(
988 res, expected,
989 "Expected scale_unsigned::<{}, {:#x}>({:#x}, {}, {}) to produce \
990 {:#x}; got {:#x}",
991 ROUND_UP, LIMIT, value, numerator, denominator, expected, res
992 );
993 }
994 }
995 }
996 }
997
998 #[test]
999 fn test_scale_unsigned_matches_reference() {
1000 macro_rules! check_against_reference_for_limits {
1001 ($($limit:expr),* $(,)?) => {
1002 $(
1003 check_against_reference::<false, { $limit }>();
1004 check_against_reference::<true, { $limit }>();
1005 )*
1006 };
1007 }
1008
1009 check_against_reference_for_limits!(0, 1, 2, 100, POSITIVE_LIMIT, NEGATIVE_LIMIT, u64::MAX,);
1010 }
1011}