1 use csl::PageRangeFormat;
2
3 /// Returns the second number with the page range format applied.
truncate_prf(prf: PageRangeFormat, first: u32, mut second: u32) -> u324 pub fn truncate_prf(prf: PageRangeFormat, first: u32, mut second: u32) -> u32 {
5 second = expand(first, second);
6 match prf {
7 PageRangeFormat::Chicago => {
8 let mod100 = first % 100;
9 let delta = second - first;
10 if first < 100 || mod100 == 0 {
11 second
12 } else if mod100 < 10 && delta < 90 {
13 truncate_diff(first, second, 1)
14 } else if closest_smaller_power_of_10(first) == 1000 {
15 let chopped = truncate_diff(first, second, 2);
16 if closest_smaller_power_of_10(chopped) == 100 {
17 // force 4 digits if 3 are different
18 return truncate_diff(first, second, 4);
19 }
20 chopped
21 } else {
22 truncate_diff(first, second, 2)
23 }
24 }
25 PageRangeFormat::Minimal => truncate_diff(first, second, 1),
26 PageRangeFormat::MinimalTwo => truncate_diff(first, second, 2),
27 PageRangeFormat::Expanded => second,
28 }
29 }
30
31 #[test]
page_range_chicago()32 fn page_range_chicago() {
33 fn go(a: u32, b: u32) -> u32 {
34 truncate_prf(PageRangeFormat::Chicago, a, b)
35 }
36 // https://docs.citationstyles.org/en/stable/specification.html#appendix-v-page-range-formats
37 // 1
38 assert_eq!(go(3, 10), 10);
39 assert_eq!(go(71, 72), 72);
40 // 2
41 assert_eq!(go(100, 104), 104);
42 assert_eq!(go(600, 613), 613);
43 assert_eq!(go(1100, 1123), 1123);
44 // 3
45 assert_eq!(go(101, 108), 8);
46 assert_eq!(go(107, 108), 8);
47 assert_eq!(go(505, 517), 17);
48 assert_eq!(go(1002, 1006), 6);
49 // 4
50 assert_eq!(go(321, 325), 25);
51 assert_eq!(go(415, 532), 532);
52 assert_eq!(go(11564, 11568), 68);
53 assert_eq!(go(13792, 13803), 803);
54 // 5 (force 4 digits where 3 are different)
55 assert_eq!(go(1496, 1504), 1504);
56 assert_eq!(go(2787, 2816), 2816);
57 // but if only two digits different, don't
58 assert_eq!(go(1486, 1496), 96);
59 }
60
61 #[test]
test_truncate_diff()62 fn test_truncate_diff() {
63 assert_eq!(truncate_diff(101, 105, 1), 5);
64 assert_eq!(truncate_diff(121, 125, 1), 5);
65 assert_eq!(truncate_diff(121, 125, 2), 25);
66 assert_eq!(truncate_diff(121, 125, 3), 125);
67 }
68
truncate_diff(a: u32, b: u32, min: u32) -> u3269 fn truncate_diff(a: u32, b: u32, min: u32) -> u32 {
70 if b < a {
71 return b;
72 }
73 let mut diff_started = false;
74 let mut acc = 0u32;
75 let mut iter_a = DigitsBase10::new(a);
76 let mut iter_b = DigitsBase10::new(b);
77 // fast forward iter_a until they have the same mask i.e. same remaining digit length
78 while iter_a.mask > iter_b.mask {
79 iter_a.next();
80 }
81 while iter_b.mask > iter_a.mask {
82 if let Some(b_dig) = iter_b.next() {
83 diff_started = true;
84 acc *= 10;
85 acc += b_dig as u32;
86 }
87 }
88 let min_mask = 10_u32.pow(min);
89 if iter_a.mask * 10 == min_mask {
90 diff_started = true;
91 }
92 // Primitive zip so we can keep access to iter_a
93 while let (Some(a_dig), Some(b_dig)) = (iter_a.next(), iter_b.next()) {
94 if diff_started || a_dig != b_dig {
95 diff_started = true;
96 acc *= 10;
97 acc += b_dig as u32;
98 }
99 if iter_a.mask * 10 == min_mask {
100 diff_started = true;
101 }
102 }
103 acc
104 }
105
106 #[test]
test_expand()107 fn test_expand() {
108 assert_eq!(expand(103, 4), 104);
109 assert_eq!(expand(133, 4), 134);
110 assert_eq!(expand(133, 54), 154);
111 }
112
expand(a: u32, b: u32) -> u32113 fn expand(a: u32, b: u32) -> u32 {
114 let mask = closest_smaller_power_of_10(b) * 10;
115 (a - (a % mask)) + (b % mask)
116 }
117
118 // Thanks to timotree3 on the Rust users forum for writing this already
119 // https://users.rust-lang.org/t/iterate-through-digits-of-a-number/34465/9
120
121 pub struct DigitsBase10 {
122 mask: u32,
123 num: u32,
124 }
125
126 impl Iterator for DigitsBase10 {
127 type Item = u8;
128
next(&mut self) -> Option<Self::Item>129 fn next(&mut self) -> Option<Self::Item> {
130 if self.mask == 0 {
131 return None;
132 }
133
134 let digit = self.num / self.mask % 10;
135 self.mask /= 10;
136
137 Some(digit as u8)
138 }
139 }
140
closest_smaller_power_of_10(num: u32) -> u32141 fn closest_smaller_power_of_10(num: u32) -> u32 {
142 let answer = 10_f64.powf((num as f64).log10().floor()) as u32;
143
144 // these properties need to hold. I think they do, but the float conversions
145 // might mess things up...
146 debug_assert!(answer <= num);
147 debug_assert!(answer > num / 10);
148 answer
149 }
150
151 impl DigitsBase10 {
new(num: u32) -> Self152 pub fn new(num: u32) -> Self {
153 let mask = if num == 0 {
154 1
155 } else {
156 closest_smaller_power_of_10(num)
157 };
158
159 DigitsBase10 { mask, num }
160 }
161 }
162