1use std::convert::TryFrom;
2use std::ops::Range;
3
4use crate::coord::ranged1d::{
5 AsRangedCoord, DefaultFormatting, DiscreteRanged, KeyPointHint, NoDefaultFormatting, Ranged,
6 ReversibleRanged, ValueFormatter,
7};
8
9macro_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
38macro_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}
46macro_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}
61macro_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
109macro_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
234gen_key_points_comp!(float, compute_f32_key_points, f32);
235gen_key_points_comp!(float, compute_f64_key_points, f64);
236gen_key_points_comp!(integer, compute_i32_key_points, i32);
237gen_key_points_comp!(integer, compute_u32_key_points, u32);
238gen_key_points_comp!(integer, compute_i64_key_points, i64);
239gen_key_points_comp!(integer, compute_u64_key_points, u64);
240gen_key_points_comp!(integer, compute_i128_key_points, i128);
241gen_key_points_comp!(integer, compute_u128_key_points, u128);
242gen_key_points_comp!(integer, compute_isize_key_points, isize);
243gen_key_points_comp!(integer, compute_usize_key_points, usize);
244
245make_numeric_coord!(
246 f32,
247 RangedCoordf32,
248 compute_f32_key_points,
249 "The ranged coordinate for type f32",
250 NoDefaultFormatting
251);
252impl_reverse_mapping_trait!(f32, RangedCoordf32);
253impl ValueFormatter<f32> for RangedCoordf32 {
254 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}
263make_numeric_coord!(
264 f64,
265 RangedCoordf64,
266 compute_f64_key_points,
267 "The ranged coordinate for type f64",
268 NoDefaultFormatting
269);
270impl_reverse_mapping_trait!(f64, RangedCoordf64);
271impl ValueFormatter<f64> for RangedCoordf64 {
272 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}
281make_numeric_coord!(
282 u32,
283 RangedCoordu32,
284 compute_u32_key_points,
285 "The ranged coordinate for type u32"
286);
287make_numeric_coord!(
288 i32,
289 RangedCoordi32,
290 compute_i32_key_points,
291 "The ranged coordinate for type i32"
292);
293make_numeric_coord!(
294 u64,
295 RangedCoordu64,
296 compute_u64_key_points,
297 "The ranged coordinate for type u64"
298);
299make_numeric_coord!(
300 i64,
301 RangedCoordi64,
302 compute_i64_key_points,
303 "The ranged coordinate for type i64"
304);
305make_numeric_coord!(
306 u128,
307 RangedCoordu128,
308 compute_u128_key_points,
309 "The ranged coordinate for type u128"
310);
311make_numeric_coord!(
312 i128,
313 RangedCoordi128,
314 compute_i128_key_points,
315 "The ranged coordinate for type i128"
316);
317make_numeric_coord!(
318 usize,
319 RangedCoordusize,
320 compute_usize_key_points,
321 "The ranged coordinate for type usize"
322);
323make_numeric_coord!(
324 isize,
325 RangedCoordisize,
326 compute_isize_key_points,
327 "The ranged coordinate for type isize"
328);
329
330impl_discrete_trait!(RangedCoordu32);
331impl_discrete_trait!(RangedCoordi32);
332impl_discrete_trait!(RangedCoordu64);
333impl_discrete_trait!(RangedCoordi64);
334impl_discrete_trait!(RangedCoordu128);
335impl_discrete_trait!(RangedCoordi128);
336impl_discrete_trait!(RangedCoordusize);
337impl_discrete_trait!(RangedCoordisize);
338
339impl_ranged_type_trait!(f32, RangedCoordf32);
340impl_ranged_type_trait!(f64, RangedCoordf64);
341impl_ranged_type_trait!(i32, RangedCoordi32);
342impl_ranged_type_trait!(u32, RangedCoordu32);
343impl_ranged_type_trait!(i64, RangedCoordi64);
344impl_ranged_type_trait!(u64, RangedCoordu64);
345impl_ranged_type_trait!(i128, RangedCoordi128);
346impl_ranged_type_trait!(u128, RangedCoordu128);
347impl_ranged_type_trait!(isize, RangedCoordisize);
348impl_ranged_type_trait!(usize, RangedCoordusize);
349
350#[cfg(test)]
351mod test {
352 use super::*;
353 #[test]
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]
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]
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]
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]
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]
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]
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]
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]
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]
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]
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