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 = ((bits & ((1 << 52) - 1)) | (1 << 52)) as u128;
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 self.num as f64 / self.den as f64
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 = (max as u64 * self.den as u64 / self.num as u64).max(1) as u128;
66 loop {
67 let (num, den) = limit_denominator(self.num as u128, self.den as u128, bound);
68 if num <= max as u128 || bound == 1 {
69 return Self {
70 num: num.min(max as u128) as u32,
71 den: den as u32,
72 };
73 }
74 bound -= 1;
75 }
76 }
77}
78
79fn limit_denominator(num: u128, den: u128, max_den: u128) -> (u128, u128) {
83 let g = gcd(num, den);
84 let (num, den) = (num / g, den / g);
85 if den <= max_den {
86 return (num, den);
87 }
88 let (mut p0, mut q0, mut p1, mut q1) = (0u128, 1u128, 1u128, 0u128);
89 let (mut n, mut d) = (num, den);
90 loop {
91 let a = n / d;
92 let q2 = q0 + a * q1;
93 if q2 > max_den {
94 break;
95 }
96 (p0, q0, p1, q1) = (p1, q1, p0 + a * p1, q2);
97 (n, d) = (d, n - a * d);
98 }
99 let k = (max_den - q0) / q1;
100 if 2 * d * (q0 + k * q1) <= den {
101 (p1, q1)
102 } else {
103 (p0 + k * p1, q0 + k * q1)
104 }
105}
106
107fn gcd(mut a: u128, mut b: u128) -> u128 {
108 while b != 0 {
109 (a, b) = (b, a % b);
110 }
111 a.max(1)
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117
118 fn rate(num: u32, den: u32) -> FrameRate {
119 FrameRate { num, den }
120 }
121
122 #[test]
125 fn a_rate_carried_as_its_fraction_reads_back_exactly() {
126 for n in [24, 25, 30, 48, 50, 60, 72, 75, 90, 100, 120, 144, 165, 240] {
127 assert_eq!(FrameRate::of(n as f64), rate(n, 1));
128 let g = gcd(n as u128 * 1000, 1001) as u32;
129 assert_eq!(
130 FrameRate::of(n as f64 * 1000.0 / 1001.0),
131 rate(n * 1000 / g, 1001 / g),
132 "{n}000/1001"
133 );
134 }
135 assert_eq!(FrameRate::of(60000.0 / 1001.0).fps(), 60000.0 / 1001.0);
136 }
137
138 #[test]
141 fn a_decimal_rate_reads_as_its_decimal_fraction() {
142 assert_eq!(FrameRate::of(59.94), rate(2997, 50));
143 assert_eq!(FrameRate::of(119.88), rate(2997, 25));
144 assert_eq!(FrameRate::of(143.856), rate(17982, 125));
145 assert_eq!(FrameRate::of(23.976), rate(2997, 125));
146 assert_eq!(FrameRate::of(29.97), rate(2997, 100));
147 assert_eq!(FrameRate::of(12.5), rate(25, 2));
148 for (decimal, n) in [(59.94, 60), (119.88, 120), (143.856, 144)] {
149 let ntsc = n as f64 * 1000.0 / 1001.0;
150 assert!((FrameRate::of(decimal).fps() - ntsc).abs() / ntsc < 1.1e-6);
151 }
152 }
153
154 #[test]
158 fn any_other_rate_reads_within_a_thousandth() {
159 for fps in [
160 53.28900001,
161 59.9512345,
162 143.98123,
163 7.3,
164 999.9999,
165 1.0000001,
166 164.987654321,
167 60.0005,
168 1.0004999,
169 ] {
170 let r = FrameRate::of(fps);
171 assert!(r.den <= 1001, "{fps}: {r:?}");
172 assert!((r.fps() - fps).abs() < 1e-3, "{fps}: {r:?} = {}", r.fps());
173 }
174 assert_eq!(FrameRate::of(59.9400599), rate(60000, 1001));
175 assert_eq!(FrameRate::of(119.880120), rate(120000, 1001));
176 }
177
178 #[test]
179 fn a_rate_outside_the_span_reads_at_its_edge() {
180 assert_eq!(FrameRate::of(0.0), rate(1, 1));
181 assert_eq!(FrameRate::of(-5.0), rate(1, 1));
182 assert_eq!(FrameRate::of(f64::NAN), rate(1, 1));
183 assert_eq!(FrameRate::of(f64::INFINITY), rate(1000, 1));
184 assert_eq!(FrameRate::of(5000.0), rate(1000, 1));
185 }
186
187 #[test]
189 fn a_level_is_sized_for_the_rate_rounded_up() {
190 assert_eq!(rate(60000, 1001).ceil(), 60);
191 assert_eq!(rate(144000, 1001).ceil(), 144);
192 assert_eq!(rate(60, 1).ceil(), 60);
193 assert_eq!(rate(2997, 50).ceil(), 60);
194 }
195
196 #[test]
199 fn a_narrow_field_takes_the_nearest_fraction_that_fits() {
200 assert_eq!(rate(60000, 1001).within(0xffff), rate(60000, 1001));
201 assert_eq!(rate(60, 1).within(0xffff), rate(60, 1));
202 for n in [120, 144, 240] {
203 let exact = rate(n * 1000, 1001);
204 let fit = exact.within(0xffff);
205 assert!(fit.num <= 0xffff && fit.den <= 0xffff, "{fit:?}");
206 assert!(
207 (fit.fps() - exact.fps()).abs() / exact.fps() < 1e-7,
208 "{n}: {fit:?}"
209 );
210 }
211 assert_eq!(rate(1000, 1).within(0xffff), rate(1000, 1));
212 }
213}