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 |