1const MAX_DENOMINATOR: u128 = 1001;
21
22const MIN_FPS: f64 = 1.0;
24const MAX_FPS: f64 = 1000.0;
25
26#[derive(Clone, Copy, Debug, PartialEq, Eq)]
28pub struct FrameRate {
29 pub num: u32,
30 pub den: u32,
31}
32
33impl FrameRate {
34 pub fn of(fps: f64) -> Self {
37 let fps = if fps.is_nan() {
38 MIN_FPS
39 } else {
40 fps.clamp(MIN_FPS, MAX_FPS)
41 };
42 let bits = fps.to_bits();
43 let mantissa = u128::from((bits & ((1 << 52) - 1)) | (1 << 52));
44 let shift = 1075 - ((bits >> 52) & 0x7ff) as i32;
45 let (num, den) = limit_denominator(mantissa, 1 << shift, MAX_DENOMINATOR);
46 Self {
47 num: num as u32,
48 den: den as u32,
49 }
50 }
51
52 pub fn fps(self) -> f64 {
54 f64::from(self.num) / f64::from(self.den)
55 }
56
57 pub fn ceil(self) -> u32 {
59 self.num.div_ceil(self.den)
60 }
61
62 pub fn within(self, max: u32) -> Self {
65 let mut bound =
66 u128::from((u64::from(max) * u64::from(self.den) / u64::from(self.num)).max(1));
67 loop {
68 let (num, den) = limit_denominator(u128::from(self.num), u128::from(self.den), bound);
69 if num <= u128::from(max) || bound == 1 {
70 return Self {
71 num: num.min(u128::from(max)) as u32,
72 den: den as u32,
73 };
74 }
75 bound -= 1;
76 }
77 }
78}
79
80fn limit_denominator(num: u128, den: u128, max_den: u128) -> (u128, u128) {
84 let g = gcd(num, den);
85 let (num, den) = (num / g, den / g);
86 if den <= max_den {
87 return (num, den);
88 }
89 let (mut p0, mut q0, mut p1, mut q1) = (0u128, 1u128, 1u128, 0u128);
90 let (mut n, mut d) = (num, den);
91 loop {
92 let a = n / d;
93 let q2 = q0 + a * q1;
94 if q2 > max_den {
95 break;
96 }
97 (p0, q0, p1, q1) = (p1, q1, p0 + a * p1, q2);
98 (n, d) = (d, n - a * d);
99 }
100 let k = (max_den - q0) / q1;
101 if 2 * d * (q0 + k * q1) <= den {
102 (p1, q1)
103 } else {
104 (p0 + k * p1, q0 + k * q1)
105 }
106}
107
108fn gcd(mut a: u128, mut b: u128) -> u128 {
109 while b != 0 {
110 (a, b) = (b, a % b);
111 }
112 a.max(1)
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118
119 fn rate(num: u32, den: u32) -> FrameRate {
120 FrameRate { num, den }
121 }
122
123 #[test]
126 fn a_rate_carried_as_its_fraction_reads_back_exactly() {
127 for n in [24, 25, 30, 48, 50, 60, 72, 75, 90, 100, 120, 144, 165, 240] {
128 assert_eq!(FrameRate::of(f64::from(n)), rate(n, 1));
129 let g = gcd(u128::from(n) * 1000, 1001) as u32;
130 assert_eq!(
131 FrameRate::of(f64::from(n) * 1000.0 / 1001.0),
132 rate(n * 1000 / g, 1001 / g),
133 "{n}000/1001"
134 );
135 }
136 assert_eq!(FrameRate::of(60000.0 / 1001.0).fps(), 60000.0 / 1001.0);
137 }
138
139 #[test]
142 fn a_decimal_rate_reads_as_its_decimal_fraction() {
143 assert_eq!(FrameRate::of(59.94), rate(2997, 50));
144 assert_eq!(FrameRate::of(119.88), rate(2997, 25));
145 assert_eq!(FrameRate::of(143.856), rate(17982, 125));
146 assert_eq!(FrameRate::of(23.976), rate(2997, 125));
147 assert_eq!(FrameRate::of(29.97), rate(2997, 100));
148 assert_eq!(FrameRate::of(12.5), rate(25, 2));
149 for (decimal, n) in [(59.94, 60), (119.88, 120), (143.856, 144)] {
150 let ntsc = f64::from(n) * 1000.0 / 1001.0;
151 assert!((FrameRate::of(decimal).fps() - ntsc).abs() / ntsc < 1.1e-6);
152 }
153 }
154
155 #[test]
159 fn any_other_rate_reads_within_a_thousandth() {
160 for fps in [
161 53.28900001,
162 59.9512345,
163 143.98123,
164 7.3,
165 999.9999,
166 1.0000001,
167 164.987654321,
168 60.0005,
169 1.0004999,
170 ] {
171 let r = FrameRate::of(fps);
172 assert!(r.den <= 1001, "{fps}: {r:?}");
173 assert!((r.fps() - fps).abs() < 1e-3, "{fps}: {r:?} = {}", r.fps());
174 }
175 assert_eq!(FrameRate::of(59.9400599), rate(60000, 1001));
176 assert_eq!(FrameRate::of(119.880120), rate(120000, 1001));
177 }
178
179 #[test]
180 fn a_rate_outside_the_span_reads_at_its_edge() {
181 assert_eq!(FrameRate::of(0.0), rate(1, 1));
182 assert_eq!(FrameRate::of(-5.0), rate(1, 1));
183 assert_eq!(FrameRate::of(f64::NAN), rate(1, 1));
184 assert_eq!(FrameRate::of(f64::INFINITY), rate(1000, 1));
185 assert_eq!(FrameRate::of(5000.0), rate(1000, 1));
186 }
187
188 #[test]
190 fn a_level_is_sized_for_the_rate_rounded_up() {
191 assert_eq!(rate(60000, 1001).ceil(), 60);
192 assert_eq!(rate(144000, 1001).ceil(), 144);
193 assert_eq!(rate(60, 1).ceil(), 60);
194 assert_eq!(rate(2997, 50).ceil(), 60);
195 }
196
197 #[test]
200 fn a_narrow_field_takes_the_nearest_fraction_that_fits() {
201 assert_eq!(rate(60000, 1001).within(0xffff), rate(60000, 1001));
202 assert_eq!(rate(60, 1).within(0xffff), rate(60, 1));
203 for n in [120, 144, 240] {
204 let exact = rate(n * 1000, 1001);
205 let fit = exact.within(0xffff);
206 assert!(fit.num <= 0xffff && fit.den <= 0xffff, "{fit:?}");
207 assert!(
208 (fit.fps() - exact.fps()).abs() / exact.fps() < 1e-7,
209 "{n}: {fit:?}"
210 );
211 }
212 assert_eq!(rate(1000, 1).within(0xffff), rate(1000, 1));
213 }
214}