1 use std::convert::TryFrom;
2 use std::ops::Range;
3 
4 use crate::coord::ranged1d::{
5     AsRangedCoord, DefaultFormatting, DiscreteRanged, KeyPointHint, NoDefaultFormatting, Ranged,
6     ReversibleRanged, ValueFormatter,
7 };
8 
9 macro_rules! impl_discrete_trait {
10     ($name:ident) => {
11         impl DiscreteRanged for $name {
12             fn size(&self) -> usize {
13                 if &self.1 < &self.0 {
14                     return 0;
15                 }
16                 let values = self.1 - self.0;
17                 (values + 1) as usize
18             }
19 
20             fn index_of(&self, value: &Self::ValueType) -> Option<usize> {
21                 if value < &self.0 {
22                     return None;
23                 }
24                 let ret = value - self.0;
25                 Some(ret as usize)
26             }
27 
28             fn from_index(&self, index: usize) -> Option<Self::ValueType> {
29                 if let Ok(index) = Self::ValueType::try_from(index) {
30                     return Some(self.0 + index);
31                 }
32                 None
33             }
34         }
35     };
36 }
37 
38 macro_rules! impl_ranged_type_trait {
39     ($value:ty, $coord:ident) => {
40         impl AsRangedCoord for Range<$value> {
41             type CoordDescType = $coord;
42             type Value = $value;
43         }
44     };
45 }
46 macro_rules! impl_reverse_mapping_trait {
47     ($type:ty, $name: ident) => {
48         impl ReversibleRanged for $name {
49             fn unmap(&self, p: i32, (min, max): (i32, i32)) -> Option<$type> {
50                 if p < min.min(max) || p > max.max(min) || min == max {
51                     return None;
52                 }
53 
54                 let logical_offset = f64::from(p - min) / f64::from(max - min);
55 
56                 return Some(((self.1 - self.0) as f64 * logical_offset + self.0 as f64) as $type);
57             }
58         }
59     };
60 }
61 macro_rules! make_numeric_coord {
62     ($type:ty, $name:ident, $key_points:ident, $doc: expr, $fmt: ident) => {
63         #[doc = $doc]
64         #[derive(Clone)]
65         pub struct $name($type, $type);
66         impl From<Range<$type>> for $name {
67             fn from(range: Range<$type>) -> Self {
68                 return $name(range.start, range.end);
69             }
70         }
71         impl Ranged for $name {
72             type FormatOption = $fmt;
73             type ValueType = $type;
74             #[allow(clippy::float_cmp)]
75             fn map(&self, v: &$type, limit: (i32, i32)) -> i32 {
76                 // Corner case: If we have a range that have only one value,
77                 // then we just assign everything to the only point
78                 if self.1 == self.0 {
79                     return (limit.1 - limit.0) / 2;
80                 }
81 
82                 let logic_length = (*v as f64 - self.0 as f64) / (self.1 as f64 - self.0 as f64);
83 
84                 let actual_length = limit.1 - limit.0;
85 
86                 if actual_length == 0 {
87                     return limit.1;
88                 }
89 
90                 if actual_length > 0 {
91                     return limit.0 + (actual_length as f64 * logic_length + 1e-3).floor() as i32;
92                 } else {
93                     return limit.0 + (actual_length as f64 * logic_length - 1e-3).ceil() as i32;
94                 }
95             }
96             fn key_points<Hint: KeyPointHint>(&self, hint: Hint) -> Vec<$type> {
97                 $key_points((self.0, self.1), hint.max_num_points())
98             }
99             fn range(&self) -> Range<$type> {
100                 return self.0..self.1;
101             }
102         }
103     };
104     ($type:ty, $name:ident, $key_points:ident, $doc: expr) => {
105         make_numeric_coord!($type, $name, $key_points, $doc, DefaultFormatting);
106     };
107 }
108 
109 macro_rules! gen_key_points_comp {
110     (float, $name:ident, $type:ty) => {
111         fn $name(range: ($type, $type), max_points: usize) -> Vec<$type> {
112             if max_points == 0 {
113                 return vec![];
114             }
115 
116             let range = (range.0.min(range.1) as f64, range.1.max(range.0) as f64);
117 
118             assert!(!(range.0.is_nan() || range.1.is_nan()));
119 
120             if (range.0 - range.1).abs() < std::f64::EPSILON {
121                 return vec![range.0 as $type];
122             }
123 
124             let mut scale = (10f64).powf((range.1 - range.0).log(10.0).floor());
125             // The value granularity controls how we round the values.
126             // To avoid generating key points like 1.00000000001, we round to the nearest multiple of the
127             // value granularity.
128             // By default, we make the granularity as the 1/10 of the scale.
129             let mut value_granularity = scale / 10.0;
130             fn rem_euclid(a: f64, b: f64) -> f64 {
131                 let ret = if b > 0.0 {
132                     a - (a / b).floor() * b
133                 } else {
134                     a - (a / b).ceil() * b
135                 };
136                 if (ret - b).abs() < std::f64::EPSILON {
137                     0.0
138                 } else {
139                     ret
140                 }
141             }
142 
143             // At this point we need to make sure that the loop invariant:
144             // The scale must yield number of points than requested
145             if 1 + ((range.1 - range.0) / scale).floor() as usize > max_points {
146                 scale *= 10.0;
147                 value_granularity *= 10.0;
148             }
149 
150             'outer: loop {
151                 let old_scale = scale;
152                 for nxt in [2.0, 5.0, 10.0].iter() {
153                     let mut new_left = range.0 - rem_euclid(range.0, old_scale / nxt);
154                     if new_left < range.0 {
155                         new_left += old_scale / nxt;
156                     }
157                     let new_right = range.1 - rem_euclid(range.1, old_scale / nxt);
158 
159                     let npoints = 1.0 + ((new_right - new_left) / old_scale * nxt);
160 
161                     if npoints.round() as usize > max_points {
162                         break 'outer;
163                     }
164 
165                     scale = old_scale / nxt;
166                 }
167                 scale = old_scale / 10.0;
168                 value_granularity /= 10.0;
169             }
170 
171             let mut ret = vec![];
172             // In some extreme cases, left might be too big, so that (left + scale) - left == 0 due to
173             // floating point error.
174             // In this case, we may loop forever. To avoid this, we need to use two variables to store
175             // the current left value. So we need keep a left_base and a left_relative.
176             let left = {
177                 let mut value = range.0 - rem_euclid(range.0, scale);
178                 if value < range.0 {
179                     value += scale;
180                 }
181                 value
182             };
183             let left_base = (left / value_granularity).floor() * value_granularity;
184             let mut left_relative = left - left_base;
185             let right = range.1 - rem_euclid(range.1, scale);
186             while (right - left_relative - left_base) >= -std::f64::EPSILON {
187                 let new_left_relative =
188                     (left_relative / value_granularity).round() * value_granularity;
189                 if new_left_relative < 0.0 {
190                     left_relative += value_granularity;
191                 }
192                 ret.push((left_relative + left_base) as $type);
193                 left_relative += scale;
194             }
195             return ret;
196         }
197     };
198     (integer, $name:ident, $type:ty) => {
199         fn $name(range: ($type, $type), max_points: usize) -> Vec<$type> {
200             let mut scale: $type = 1;
201             let range = (range.0.min(range.1), range.0.max(range.1));
202             let range_size = range.1 as f64 - range.0 as f64;
203             'outer: while (range_size / scale as f64).ceil() > max_points as f64 {
204                 let next_scale = scale * 10;
205                 for new_scale in [scale * 2, scale * 5, scale * 10].iter() {
206                     scale = *new_scale;
207                     if (range_size / *new_scale as f64).ceil() < max_points as f64 {
208                         break 'outer;
209                     }
210                 }
211                 scale = next_scale;
212             }
213 
214             let (mut left, right) = (
215                 range.0 + (scale - range.0 % scale) % scale,
216                 range.1 - range.1 % scale,
217             );
218 
219             let mut ret = vec![];
220             while left <= right {
221                 ret.push(left as $type);
222                 if left < right {
223                     left += scale;
224                 } else {
225                     break;
226                 }
227             }
228 
229             return ret;
230         }
231     };
232 }
233 
234 gen_key_points_comp!(float, compute_f32_key_points, f32);
235 gen_key_points_comp!(float, compute_f64_key_points, f64);
236 gen_key_points_comp!(integer, compute_i32_key_points, i32);
237 gen_key_points_comp!(integer, compute_u32_key_points, u32);
238 gen_key_points_comp!(integer, compute_i64_key_points, i64);
239 gen_key_points_comp!(integer, compute_u64_key_points, u64);
240 gen_key_points_comp!(integer, compute_i128_key_points, i128);
241 gen_key_points_comp!(integer, compute_u128_key_points, u128);
242 gen_key_points_comp!(integer, compute_isize_key_points, isize);
243 gen_key_points_comp!(integer, compute_usize_key_points, usize);
244 
245 make_numeric_coord!(
246     f32,
247     RangedCoordf32,
248     compute_f32_key_points,
249     "The ranged coordinate for type f32",
250     NoDefaultFormatting
251 );
252 impl_reverse_mapping_trait!(f32, RangedCoordf32);
253 impl ValueFormatter<f32> for RangedCoordf32 {
format(value: &f32) -> String254     fn format(value: &f32) -> String {
255         crate::data::float::FloatPrettyPrinter {
256             allow_scientific: false,
257             min_decimal: 1,
258             max_decimal: 5,
259         }
260         .print(*value as f64)
261     }
262 }
263 make_numeric_coord!(
264     f64,
265     RangedCoordf64,
266     compute_f64_key_points,
267     "The ranged coordinate for type f64",
268     NoDefaultFormatting
269 );
270 impl_reverse_mapping_trait!(f64, RangedCoordf64);
271 impl ValueFormatter<f64> for RangedCoordf64 {
format(value: &f64) -> String272     fn format(value: &f64) -> String {
273         crate::data::float::FloatPrettyPrinter {
274             allow_scientific: false,
275             min_decimal: 1,
276             max_decimal: 5,
277         }
278         .print(*value)
279     }
280 }
281 make_numeric_coord!(
282     u32,
283     RangedCoordu32,
284     compute_u32_key_points,
285     "The ranged coordinate for type u32"
286 );
287 make_numeric_coord!(
288     i32,
289     RangedCoordi32,
290     compute_i32_key_points,
291     "The ranged coordinate for type i32"
292 );
293 make_numeric_coord!(
294     u64,
295     RangedCoordu64,
296     compute_u64_key_points,
297     "The ranged coordinate for type u64"
298 );
299 make_numeric_coord!(
300     i64,
301     RangedCoordi64,
302     compute_i64_key_points,
303     "The ranged coordinate for type i64"
304 );
305 make_numeric_coord!(
306     u128,
307     RangedCoordu128,
308     compute_u128_key_points,
309     "The ranged coordinate for type u128"
310 );
311 make_numeric_coord!(
312     i128,
313     RangedCoordi128,
314     compute_i128_key_points,
315     "The ranged coordinate for type i128"
316 );
317 make_numeric_coord!(
318     usize,
319     RangedCoordusize,
320     compute_usize_key_points,
321     "The ranged coordinate for type usize"
322 );
323 make_numeric_coord!(
324     isize,
325     RangedCoordisize,
326     compute_isize_key_points,
327     "The ranged coordinate for type isize"
328 );
329 
330 impl_discrete_trait!(RangedCoordu32);
331 impl_discrete_trait!(RangedCoordi32);
332 impl_discrete_trait!(RangedCoordu64);
333 impl_discrete_trait!(RangedCoordi64);
334 impl_discrete_trait!(RangedCoordu128);
335 impl_discrete_trait!(RangedCoordi128);
336 impl_discrete_trait!(RangedCoordusize);
337 impl_discrete_trait!(RangedCoordisize);
338 
339 impl_ranged_type_trait!(f32, RangedCoordf32);
340 impl_ranged_type_trait!(f64, RangedCoordf64);
341 impl_ranged_type_trait!(i32, RangedCoordi32);
342 impl_ranged_type_trait!(u32, RangedCoordu32);
343 impl_ranged_type_trait!(i64, RangedCoordi64);
344 impl_ranged_type_trait!(u64, RangedCoordu64);
345 impl_ranged_type_trait!(i128, RangedCoordi128);
346 impl_ranged_type_trait!(u128, RangedCoordu128);
347 impl_ranged_type_trait!(isize, RangedCoordisize);
348 impl_ranged_type_trait!(usize, RangedCoordusize);
349 
350 #[cfg(test)]
351 mod test {
352     use super::*;
353     #[test]
test_key_points()354     fn test_key_points() {
355         let kp = compute_i32_key_points((0, 999), 28);
356 
357         assert!(kp.len() > 0);
358         assert!(kp.len() <= 28);
359 
360         let kp = compute_f64_key_points((-1.2, 1.2), 1);
361         assert!(kp.len() == 1);
362 
363         let kp = compute_f64_key_points((-1.2, 1.2), 0);
364         assert!(kp.len() == 0);
365     }
366 
367     #[test]
test_linear_coord_map()368     fn test_linear_coord_map() {
369         let coord: RangedCoordu32 = (0..20).into();
370         assert_eq!(coord.key_points(11).len(), 11);
371         assert_eq!(coord.key_points(11)[0], 0);
372         assert_eq!(coord.key_points(11)[10], 20);
373         assert_eq!(coord.map(&5, (0, 100)), 25);
374 
375         let coord: RangedCoordf32 = (0f32..20f32).into();
376         assert_eq!(coord.map(&5.0, (0, 100)), 25);
377     }
378 
379     #[test]
test_linear_coord_system()380     fn test_linear_coord_system() {
381         let _coord =
382             crate::coord::ranged2d::cartesian::Cartesian2d::<RangedCoordu32, RangedCoordu32>::new(
383                 0..10,
384                 0..10,
385                 (0..1024, 0..768),
386             );
387     }
388 
389     #[test]
test_coord_unmap()390     fn test_coord_unmap() {
391         let coord: RangedCoordu32 = (0..20).into();
392         let pos = coord.map(&5, (1000, 2000));
393         let value = coord.unmap(pos, (1000, 2000));
394         assert_eq!(value, Some(5));
395     }
396 
397     #[test]
regression_test_issue_253_zero_sized_coord_not_hang()398     fn regression_test_issue_253_zero_sized_coord_not_hang() {
399         let coord: RangedCoordf32 = (0.0..0.0).into();
400         let _points = coord.key_points(10);
401     }
402 
403     #[test]
test_small_coord()404     fn test_small_coord() {
405         let coord: RangedCoordf64 = (0.0..1e-25).into();
406         let points = coord.key_points(10);
407         assert!(points.len() > 0);
408     }
409 
410     #[test]
regression_test_issue_255_reverse_f32_coord_no_hang()411     fn regression_test_issue_255_reverse_f32_coord_no_hang() {
412         let coord: RangedCoordf32 = (10.0..0.0).into();
413         let _points = coord.key_points(10);
414     }
415 
416     #[test]
regession_test_issue_358_key_points_no_hang()417     fn regession_test_issue_358_key_points_no_hang() {
418         let coord: RangedCoordf64 = (-200.0..801.0).into();
419         let points = coord.key_points(500);
420         assert!(points.len() <= 500);
421     }
422 
423     #[test]
regression_test_issue_358_key_points_no_hang_2()424     fn regression_test_issue_358_key_points_no_hang_2() {
425         let coord: RangedCoordf64 = (10000000000001f64..10000000000002f64).into();
426         let points = coord.key_points(500);
427         assert!(points.len() <= 500);
428     }
429 
430     #[test]
test_coord_follows_hint()431     fn test_coord_follows_hint() {
432         let coord: RangedCoordf64 = (1.0..2.0).into();
433         let points = coord.key_points(6);
434         assert_eq!(points.len(), 6);
435         assert_eq!(points[0], 1.0);
436         let coord: RangedCoordf64 = (1.0..125.0).into();
437         let points = coord.key_points(12);
438         assert_eq!(points.len(), 12);
439         let coord: RangedCoordf64 = (0.9995..1.0005).into();
440         let points = coord.key_points(11);
441         assert_eq!(points.len(), 11);
442         let coord: RangedCoordf64 = (0.9995..1.0005).into();
443         let points = coord.key_points(2);
444         assert!(points.len() <= 2);
445     }
446 
447     #[test]
regression_test_issue_304_intmax_keypoint_no_panic()448     fn regression_test_issue_304_intmax_keypoint_no_panic() {
449         let coord: RangedCoordu32 = (0..u32::MAX).into();
450         let p = coord.key_points(10);
451         assert!(p.len() > 0 && p.len() <= 10);
452     }
453 }
454