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