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