/rust/registry/src/index.crates.io-6f17d22bba15001f/calendrical_calculations-0.1.2/src/helpers.rs
Line | Count | Source (jump to first uncovered line) |
1 | | // This file is part of ICU4X. |
2 | | // |
3 | | // The contents of this file implement algorithms from Calendrical Calculations |
4 | | // by Reingold & Dershowitz, Cambridge University Press, 4th edition (2018), |
5 | | // which have been released as Lisp code at <https://github.com/EdReingold/calendar-code2/> |
6 | | // under the Apache-2.0 license. Accordingly, this file is released under |
7 | | // the Apache License, Version 2.0 which can be found at the calendrical_calculations |
8 | | // package root or at http://www.apache.org/licenses/LICENSE-2.0. |
9 | | |
10 | | use crate::astronomy::Location; |
11 | | use crate::rata_die::{Moment, RataDie}; |
12 | | #[allow(unused_imports)] |
13 | | use core_maths::*; |
14 | | |
15 | | pub(crate) trait IntegerRoundings { |
16 | | fn div_ceil(self, rhs: Self) -> Self; |
17 | | } |
18 | | |
19 | | impl IntegerRoundings for i64 { |
20 | | // copied from std |
21 | 0 | fn div_ceil(self, rhs: Self) -> Self { |
22 | 0 | let d = self / rhs; |
23 | 0 | let r = self % rhs; |
24 | 0 | if (r > 0 && rhs > 0) || (r < 0 && rhs < 0) { |
25 | 0 | d + 1 |
26 | | } else { |
27 | 0 | d |
28 | | } |
29 | 0 | } |
30 | | } |
31 | | |
32 | | #[test] |
33 | | fn test_div_ceil() { |
34 | | assert_eq!(IntegerRoundings::div_ceil(i64::MIN, 1), i64::MIN); |
35 | | assert_eq!( |
36 | | IntegerRoundings::div_ceil(i64::MIN, 2), |
37 | | -4611686018427387904 |
38 | | ); |
39 | | assert_eq!( |
40 | | IntegerRoundings::div_ceil(i64::MIN, 3), |
41 | | -3074457345618258602 |
42 | | ); |
43 | | |
44 | | assert_eq!(IntegerRoundings::div_ceil(-10, 1), -10); |
45 | | assert_eq!(IntegerRoundings::div_ceil(-10, 2), -5); |
46 | | assert_eq!(IntegerRoundings::div_ceil(-10, 3), -3); |
47 | | |
48 | | assert_eq!(IntegerRoundings::div_ceil(-9, 1), -9); |
49 | | assert_eq!(IntegerRoundings::div_ceil(-9, 2), -4); |
50 | | assert_eq!(IntegerRoundings::div_ceil(-9, 3), -3); |
51 | | |
52 | | assert_eq!(IntegerRoundings::div_ceil(-8, 1), -8); |
53 | | assert_eq!(IntegerRoundings::div_ceil(-8, 2), -4); |
54 | | assert_eq!(IntegerRoundings::div_ceil(-8, 3), -2); |
55 | | |
56 | | assert_eq!(IntegerRoundings::div_ceil(-2, 1), -2); |
57 | | assert_eq!(IntegerRoundings::div_ceil(-2, 2), -1); |
58 | | assert_eq!(IntegerRoundings::div_ceil(-2, 3), 0); |
59 | | |
60 | | assert_eq!(IntegerRoundings::div_ceil(-1, 1), -1); |
61 | | assert_eq!(IntegerRoundings::div_ceil(-1, 2), 0); |
62 | | assert_eq!(IntegerRoundings::div_ceil(-1, 3), 0); |
63 | | |
64 | | assert_eq!(IntegerRoundings::div_ceil(0, 1), 0); |
65 | | assert_eq!(IntegerRoundings::div_ceil(0, 2), 0); |
66 | | assert_eq!(IntegerRoundings::div_ceil(0, 3), 0); |
67 | | |
68 | | assert_eq!(IntegerRoundings::div_ceil(1, 1), 1); |
69 | | assert_eq!(IntegerRoundings::div_ceil(1, 2), 1); |
70 | | assert_eq!(IntegerRoundings::div_ceil(1, 3), 1); |
71 | | |
72 | | assert_eq!(IntegerRoundings::div_ceil(2, 1), 2); |
73 | | assert_eq!(IntegerRoundings::div_ceil(2, 2), 1); |
74 | | assert_eq!(IntegerRoundings::div_ceil(2, 3), 1); |
75 | | |
76 | | assert_eq!(IntegerRoundings::div_ceil(8, 1), 8); |
77 | | assert_eq!(IntegerRoundings::div_ceil(8, 2), 4); |
78 | | assert_eq!(IntegerRoundings::div_ceil(8, 3), 3); |
79 | | |
80 | | assert_eq!(IntegerRoundings::div_ceil(9, 1), 9); |
81 | | assert_eq!(IntegerRoundings::div_ceil(9, 2), 5); |
82 | | assert_eq!(IntegerRoundings::div_ceil(9, 3), 3); |
83 | | |
84 | | assert_eq!(IntegerRoundings::div_ceil(10, 1), 10); |
85 | | assert_eq!(IntegerRoundings::div_ceil(10, 2), 5); |
86 | | assert_eq!(IntegerRoundings::div_ceil(10, 3), 4); |
87 | | |
88 | | assert_eq!(IntegerRoundings::div_ceil(i64::MAX, 1), 9223372036854775807); |
89 | | assert_eq!(IntegerRoundings::div_ceil(i64::MAX, 2), 4611686018427387904); |
90 | | assert_eq!(IntegerRoundings::div_ceil(i64::MAX, 3), 3074457345618258603); |
91 | | |
92 | | for n in -100..100 { |
93 | | for d in 1..5 { |
94 | | let x1 = IntegerRoundings::div_ceil(n, d); |
95 | | let x2 = (n as f64 / d as f64).ceil(); |
96 | | assert_eq!(x1, x2 as i64); |
97 | | } |
98 | | } |
99 | | } |
100 | | |
101 | | // Highest power is *last* |
102 | 0 | pub(crate) fn poly(x: f64, coeffs: &[f64]) -> f64 { |
103 | 0 | coeffs.iter().rev().fold(0.0, |a, c| a * x + c) |
104 | 0 | } |
105 | | |
106 | | // A generic function that finds a value within an interval |
107 | | // where a certain condition is satisfied. |
108 | 0 | pub(crate) fn binary_search( |
109 | 0 | mut l: f64, |
110 | 0 | mut h: f64, |
111 | 0 | test: impl Fn(f64) -> bool, |
112 | 0 | epsilon: f64, |
113 | 0 | ) -> f64 { |
114 | 0 | debug_assert!(l < h); |
115 | | |
116 | | loop { |
117 | 0 | let mid = l + (h - l) / 2.0; |
118 | | |
119 | | // Determine which direction to go. `test` returns true if we need to go left. |
120 | 0 | (l, h) = if test(mid) { (l, mid) } else { (mid, h) }; |
121 | | |
122 | | // When the interval width reaches `epsilon`, return the current midpoint. |
123 | 0 | if (h - l) < epsilon { |
124 | 0 | return mid; |
125 | 0 | } |
126 | | } |
127 | 0 | } Unexecuted instantiation: calendrical_calculations::helpers::binary_search::<calendrical_calculations::helpers::invert_angular<<calendrical_calculations::astronomy::Astronomical>::lunar_phase_at_or_before::{closure#0}>::{closure#0}> Unexecuted instantiation: calendrical_calculations::helpers::binary_search::<<calendrical_calculations::astronomy::Astronomical>::moonset::{closure#0}> |
128 | | |
129 | 0 | pub(crate) fn next_moment<F>(mut index: Moment, location: Location, condition: F) -> RataDie |
130 | 0 | where |
131 | 0 | F: Fn(Moment, Location) -> bool, |
132 | 0 | { |
133 | | loop { |
134 | 0 | if condition(index, location) { |
135 | 0 | return index.as_rata_die(); |
136 | 0 | } |
137 | 0 | index += 1.0; |
138 | | } |
139 | 0 | } |
140 | | |
141 | 0 | pub(crate) fn next<F>(mut index: RataDie, condition: F) -> RataDie |
142 | 0 | where |
143 | 0 | F: Fn(RataDie) -> bool, |
144 | 0 | { |
145 | | loop { |
146 | 0 | if condition(index) { |
147 | 0 | return index; |
148 | 0 | } |
149 | 0 | index += 1; |
150 | | } |
151 | 0 | } |
152 | | |
153 | 0 | pub(crate) fn next_u8<F>(mut index: u8, condition: F) -> u8 |
154 | 0 | where |
155 | 0 | F: Fn(u8) -> bool, |
156 | 0 | { |
157 | | loop { |
158 | 0 | if condition(index) { |
159 | 0 | return index; |
160 | 0 | } |
161 | 0 | index += 1; |
162 | | } |
163 | 0 | } |
164 | | |
165 | | // "Final" is a reserved keyword in rust, which explains the naming convention here. |
166 | 0 | pub(crate) fn final_func<F>(mut index: i32, condition: F) -> i32 |
167 | 0 | where |
168 | 0 | F: Fn(i32) -> bool, |
169 | 0 | { |
170 | 0 | while condition(index) { |
171 | 0 | index += 1; |
172 | 0 | } |
173 | 0 | index - 1 |
174 | 0 | } |
175 | | |
176 | | #[test] |
177 | | fn test_binary_search() { |
178 | | struct TestCase { |
179 | | test_fn: fn(f64) -> bool, |
180 | | range: (f64, f64), |
181 | | expected: f64, |
182 | | } |
183 | | |
184 | | let test_cases = [ |
185 | | TestCase { |
186 | | test_fn: |x: f64| x >= 4.0, |
187 | | range: (0.0, 10.0), |
188 | | expected: 4.0, |
189 | | }, |
190 | | TestCase { |
191 | | test_fn: |x: f64| x * x >= 2.0, |
192 | | range: (0.0, 2.0), |
193 | | expected: 2.0f64.sqrt(), |
194 | | }, |
195 | | TestCase { |
196 | | test_fn: |x: f64| x >= -4.0, |
197 | | range: (-10.0, 0.0), |
198 | | expected: -4.0, |
199 | | }, |
200 | | TestCase { |
201 | | test_fn: |x: f64| x >= 0.0, |
202 | | range: (0.0, 10.0), |
203 | | expected: 0.0, |
204 | | }, |
205 | | TestCase { |
206 | | test_fn: |x: f64| x > 10.0, |
207 | | range: (0.0, 10.0), |
208 | | expected: 10.0, |
209 | | }, |
210 | | ]; |
211 | | |
212 | | for case in test_cases { |
213 | | let result = binary_search(case.range.0, case.range.1, case.test_fn, 1e-4); |
214 | | assert!((result - case.expected).abs() < 0.0001); |
215 | | } |
216 | | } |
217 | | |
218 | 0 | pub(crate) fn invert_angular<F: Fn(f64) -> f64>(f: F, y: f64, r: (f64, f64)) -> f64 { |
219 | 0 | binary_search(r.0, r.1, |x| (f(x) - y).rem_euclid(360.0) < 180.0, 1e-5) |
220 | 0 | } |
221 | | |
222 | | #[test] |
223 | | fn test_invert_angular() { |
224 | | struct TestCase { |
225 | | f: fn(f64) -> f64, |
226 | | y: f64, |
227 | | r: (f64, f64), |
228 | | expected: f64, |
229 | | } |
230 | | |
231 | | fn f1(x: f64) -> f64 { |
232 | | (2.0 * x).rem_euclid(360.0) |
233 | | } |
234 | | |
235 | | fn f2(x: f64) -> f64 { |
236 | | (3.0 * x).rem_euclid(360.0) |
237 | | } |
238 | | |
239 | | fn f3(x: f64) -> f64 { |
240 | | (x).rem_euclid(360.0) |
241 | | } |
242 | | // tolerance for comparing floating points. |
243 | | let tolerance = 1e-5; |
244 | | |
245 | | let test_cases = [ |
246 | | TestCase { |
247 | | f: f1, |
248 | | y: 4.0, |
249 | | r: (0.0, 10.0), |
250 | | expected: 4.0, |
251 | | }, |
252 | | TestCase { |
253 | | f: f2, |
254 | | y: 6.0, |
255 | | r: (0.0, 20.0), |
256 | | expected: 6.0, |
257 | | }, |
258 | | TestCase { |
259 | | f: f3, |
260 | | y: 400.0, |
261 | | r: (0.0, 10.0), |
262 | | expected: 10.0, |
263 | | }, |
264 | | TestCase { |
265 | | f: f3, |
266 | | y: 0.0, |
267 | | r: (0.0, 10.0), |
268 | | expected: 0.0, |
269 | | }, |
270 | | TestCase { |
271 | | f: f3, |
272 | | y: 10.0, |
273 | | r: (0.0, 10.0), |
274 | | expected: 10.0, |
275 | | }, |
276 | | ]; |
277 | | |
278 | | for case in test_cases { |
279 | | let x = invert_angular(case.f, case.y, case.r); |
280 | | assert!((((case.f)(x)).rem_euclid(360.0) - case.expected).abs() < tolerance); |
281 | | } |
282 | | } |
283 | | |
284 | | /// Error returned when casting from an i32 |
285 | | #[derive(Copy, Clone, Debug)] |
286 | | #[allow(clippy::exhaustive_enums)] // enum is specific to function and has a closed set of possible values |
287 | | pub enum I32CastError { |
288 | | /// Less than i32::MIN |
289 | | BelowMin, |
290 | | /// Greater than i32::MAX |
291 | | AboveMax, |
292 | | } |
293 | | |
294 | | impl I32CastError { |
295 | 0 | pub(crate) const fn saturate(self) -> i32 { |
296 | 0 | match self { |
297 | 0 | I32CastError::BelowMin => i32::MIN, |
298 | 0 | I32CastError::AboveMax => i32::MAX, |
299 | | } |
300 | 0 | } |
301 | | } |
302 | | |
303 | | /// Convert an i64 to i32 and with information on which way it was out of bounds if so |
304 | | #[inline] |
305 | 0 | pub const fn i64_to_i32(input: i64) -> Result<i32, I32CastError> { |
306 | 0 | if input < i32::MIN as i64 { |
307 | 0 | Err(I32CastError::BelowMin) |
308 | 0 | } else if input > i32::MAX as i64 { |
309 | 0 | Err(I32CastError::AboveMax) |
310 | | } else { |
311 | 0 | Ok(input as i32) |
312 | | } |
313 | 0 | } Unexecuted instantiation: calendrical_calculations::helpers::i64_to_i32 Unexecuted instantiation: calendrical_calculations::helpers::i64_to_i32 |
314 | | |
315 | | /// Convert an i64 to i32 but saturate at th ebounds |
316 | | #[inline] |
317 | 0 | pub fn i64_to_saturated_i32(input: i64) -> i32 { |
318 | 0 | i64_to_i32(input).unwrap_or_else(|i| i.saturate()) |
319 | 0 | } Unexecuted instantiation: calendrical_calculations::helpers::i64_to_saturated_i32 Unexecuted instantiation: calendrical_calculations::helpers::i64_to_saturated_i32 |
320 | | |
321 | | #[test] |
322 | | fn test_i64_to_saturated_i32() { |
323 | | assert_eq!(i64_to_saturated_i32(i64::MIN), i32::MIN); |
324 | | assert_eq!(i64_to_saturated_i32(-2147483649), -2147483648); |
325 | | assert_eq!(i64_to_saturated_i32(-2147483648), -2147483648); |
326 | | assert_eq!(i64_to_saturated_i32(-2147483647), -2147483647); |
327 | | assert_eq!(i64_to_saturated_i32(-2147483646), -2147483646); |
328 | | assert_eq!(i64_to_saturated_i32(-100), -100); |
329 | | assert_eq!(i64_to_saturated_i32(0), 0); |
330 | | assert_eq!(i64_to_saturated_i32(100), 100); |
331 | | assert_eq!(i64_to_saturated_i32(2147483646), 2147483646); |
332 | | assert_eq!(i64_to_saturated_i32(2147483647), 2147483647); |
333 | | assert_eq!(i64_to_saturated_i32(2147483648), 2147483647); |
334 | | assert_eq!(i64_to_saturated_i32(2147483649), 2147483647); |
335 | | assert_eq!(i64_to_saturated_i32(i64::MAX), i32::MAX); |
336 | | } |