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 { |
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 | } |
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 { |
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 | } |
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] |
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 | |