Coverage Report

Created: 2025-08-13 21:02

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/home/a220/proj/radnelac/src/common/math.rs
Line
Count
Source
1
// This Source Code Form is subject to the terms of the Mozilla Public
2
// License, v. 2.0. If a copy of the MPL was not distributed with this
3
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5
use crate::common::error::CalendarError;
6
use num_traits::AsPrimitive;
7
use num_traits::Bounded;
8
use num_traits::Euclid;
9
use num_traits::FromPrimitive;
10
use num_traits::NumAssign;
11
use num_traits::ToPrimitive;
12
use num_traits::Zero;
13
use std::cmp::PartialOrd;
14
15
// https://en.m.wikipedia.org/wiki/Double-precision_floating-point_format
16
// > Between 2^52=4,503,599,627,370,496 and 2^53=9,007,199,254,740,992 the
17
// > representable numbers are exactly the integers. For the next range,
18
// > from 2^53 to 2^54, everything is multiplied by 2, so the representable
19
// > numbers are the even ones, etc. Conversely, for the previous range from
20
// > 2^51 to 2^52, the spacing is 0.5, etc.
21
// >
22
// > The spacing as a fraction of the numbers in the range from 2^n to 2^n+1
23
// > is 2^n−52.
24
25
// We want to represent seconds as fractions of a day, and represent days
26
// since any calendar's epoch as whole numbers. We should avoid using floating
27
// point in the ranges where that would be inaccurate.
28
// 1/(24 * 60 * 60) = 0.000011574074074074073499346534
29
// 2 ** (36 - 52)   = 0.000015258789062500000000000000 (n = 36 is too large)
30
// 2 ** (35 - 52)   = 0.000007629394531250000000000000 (n = 35 is small, but risks off by 1 second)
31
// 2 ** (34 - 52)   = 0.000003814697265625000000000000 (n = 34 is probably small enough)
32
// 2 ** 34          = 17179869184
33
// 2 ** 35          = 34359738368
34
// 2 ** 36          = 68719476736
35
// Converted into years, it's still a lot of time:
36
// 2 ** 34 / 365.25 = 47035918.36824093
37
38
pub const EFFECTIVE_MAX: f64 = 17179869184.0;
39
pub const EFFECTIVE_MIN: f64 = -EFFECTIVE_MAX;
40
pub const EQ_SCALE: f64 = EFFECTIVE_MAX;
41
pub const EFFECTIVE_EPSILON: f64 = 0.000003814697265625;
42
43
pub trait TermNum:
44
    NumAssign
45
    + PartialOrd
46
    + ToPrimitive
47
    + FromPrimitive
48
    + AsPrimitive<f64>
49
    + AsPrimitive<i64>
50
    + AsPrimitive<Self>
51
    + Euclid
52
    + Bounded
53
    + Copy
54
{
55
0
    fn approx_eq(self, other: Self) -> bool {
56
0
        self == other
57
0
    }
58
59
0
    fn approx_floor(self) -> Self {
60
0
        self
61
0
    }
62
63
0
    fn floor_round(self) -> Self {
64
0
        self
65
0
    }
66
67
256
    fn is_a_number(self) -> bool {
68
256
        true
69
256
    }
70
71
65.0k
    fn modulus(self, other: Self) -> Self {
72
65.0k
        let x = self;
73
65.0k
        let y = other;
74
65.0k
        Euclid::rem_euclid(&x, &y)
75
65.0k
    }
76
77
768
    fn approx_eq_iter<T: IntoIterator<Item = Self>>(x: T, y: T) -> bool {
78
768
        !x.into_iter()
79
768
            .zip(y.into_iter())
80
2.30k
            .
any768
(|(zx, zy)| !zx.approx_eq(zy))
81
768
    }
82
83
0
    fn sign(self) -> Self {
84
0
        if self.is_zero() {
85
0
            Self::zero()
86
        } else {
87
0
            Self::one()
88
        }
89
0
    }
90
91
25
    fn gcd(self, other: Self) -> Self {
92
        //LISTING 1.22 (*Calendrical Calculations: The Ultimate Edition* by Reingold & Dershowitz.)
93
25
        let x = self;
94
25
        let y = other;
95
25
        if y.is_zero() {
96
7
            x
97
        } else {
98
18
            y.gcd(x.modulus(y))
99
        }
100
25
    }
101
102
2
    fn lcm(self, other: Self) -> Self {
103
        //LISTING 1.23 (*Calendrical Calculations: The Ultimate Edition* by Reingold & Dershowitz.)
104
2
        let x = self;
105
2
        let y = other;
106
2
        (x * y) / x.gcd(y)
107
2
    }
108
109
12.0k
    fn interval_modulus(self, a: Self, b: Self) -> Self {
110
        //LISTING 1.24 (*Calendrical Calculations: The Ultimate Edition* by Reingold & Dershowitz.)
111
12.0k
        let x = self;
112
12.0k
        if a == b {
113
0
            x
114
        } else {
115
12.0k
            a + (x - a).modulus(b - a)
116
        }
117
12.0k
    }
118
119
106k
    fn adjusted_remainder(self, b: Self) -> Self {
120
        //LISTING 1.28 (*Calendrical Calculations: The Ultimate Edition* by Reingold & Dershowitz.)
121
106k
        let x = self;
122
106k
        let m = x.modulus(b);
123
106k
        if m == Self::zero() {
124
3.60k
            b
125
        } else {
126
102k
            m
127
        }
128
106k
    }
129
130
19.7k
    fn sum<U: TermNum>(f: impl Fn(U) -> Self, p: impl Fn(U) -> bool, k: U) -> Self {
131
        //LISTING 1.30 (*Calendrical Calculations: The Ultimate Edition* by Reingold & Dershowitz.)
132
        //Modified to use iteration instead of recursion
133
19.7k
        let mut result = Self::zero();
134
19.7k
        let mut i = k;
135
57.6k
        while p(i) {
136
37.9k
            result += f(i);
137
37.9k
            i += U::one();
138
37.9k
        }
139
19.7k
        result
140
19.7k
    }
141
142
219k
    fn product<U: TermNum>(f: impl Fn(U) -> Self, p: impl Fn(U) -> bool, k: U) -> Self {
143
        //LISTING 1.31 (*Calendrical Calculations: The Ultimate Edition* by Reingold & Dershowitz.)
144
        //Modified to user iteration instead of recursion
145
219k
        let mut result = Self::one();
146
219k
        let mut i = k;
147
543k
        while p(i) {
148
323k
            result *= f(i);
149
323k
            i += U::one();
150
323k
        }
151
219k
        result
152
219k
    }
153
154
55.6k
    fn validate_mixed_radix(a: &[Self], b: &[Self]) -> Result<(), CalendarError> {
155
55.6k
        if a.len() != (b.len() + 1) {
156
0
            Err(CalendarError::MixedRadixWrongSize)
157
163k
        } else if 
b.iter()55.6k
.
any55.6k
(|&bx| bx.is_zero()) {
158
0
            Err(CalendarError::MixedRadixZeroBase)
159
        } else {
160
55.6k
            Ok(())
161
        }
162
55.6k
    }
163
164
9.87k
    fn from_mixed_radix(a: &[Self], b: &[Self], k: usize) -> Result<f64, CalendarError> {
165
        //LISTING 1.41 (*Calendrical Calculations: The Ultimate Edition* by Reingold & Dershowitz.)
166
9.87k
        let n = b.len();
167
9.87k
        let as_f64 = <Self as AsPrimitive<f64>>::as_;
168
9.87k
        match TermNum::validate_mixed_radix(a, b) {
169
9.87k
            Ok(()) => (),
170
0
            Err(error) => return Err(error),
171
        };
172
173
9.87k
        let sum_mul: f64 = TermNum::sum(
174
13.4k
            |i| 
a[i]11.4k
*
TermNum::product11.4k
(|j| b[
j2.04k
], |j| j < k,
i11.4k
),
175
21.2k
            |i| i <= k,
176
            0,
177
        )
178
9.87k
        .as_();
179
180
9.87k
        let sum_div: f64 = TermNum::sum(
181
78.5k
            |i| 
as_f64(a[i])26.5k
/
as_f6426.5k
(
TermNum::product26.5k
(|j| b[
j52.0k
], |j| j < i,
k26.5k
)),
182
36.4k
            |i| i <= n,
183
9.87k
            k + 1,
184
        );
185
186
9.87k
        Ok(sum_mul + sum_div)
187
9.87k
    }
188
189
45.7k
    fn to_mixed_radix(x: f64, b: &[Self], k: usize, a: &mut [Self]) -> Result<(), CalendarError> {
190
        //LISTING 1.42 (*Calendrical Calculations: The Ultimate Edition* by Reingold & Dershowitz.)
191
45.7k
        let n = b.len();
192
45.7k
        match TermNum::validate_mixed_radix(a, b) {
193
45.7k
            Ok(()) => (),
194
0
            Err(error) => return Err(error),
195
        };
196
197
181k
        for i in 0..
(n + 1)45.7k
{
198
181k
            if i == 0 {
199
47.3k
                let 
p045.7k
:
f6445.7k
=
TermNum::product45.7k
(|j| b[
j1.53k
], |j| j < k, 0).
as_45.7k
();
200
45.7k
                let q0 = Self::from_f64((x / p0).approx_floor() as f64);
201
45.7k
                match q0 {
202
45.7k
                    Some(q) => a[i] = q,
203
0
                    None => return Err(CalendarError::ImpossibleResult),
204
                }
205
135k
            } else if i > 0 && i < k {
206
1.02k
                let 
p1512
:
f64512
=
TermNum::product512
(|j| b[
j512
], |j| j < k,
i512
).
as_512
();
207
512
                let q1 = Self::from_f64((x / p1).approx_floor() as f64);
208
512
                match q1 {
209
512
                    Some(q) => a[i] = q.modulus(b[i - 1]),
210
0
                    None => return Err(CalendarError::ImpossibleResult),
211
                }
212
135k
            } else if i >= k && i < n {
213
222k
                let 
p289.5k
:
f6489.5k
=
TermNum::product89.5k
(|j| b[
j133k
], |j| j < i,
k89.5k
).
as_89.5k
();
214
89.5k
                let q2 = Self::from_f64((x * p2).approx_floor() as f64);
215
89.5k
                match q2 {
216
89.5k
                    Some(q) => a[i] = q.modulus(b[i - 1]),
217
0
                    None => return Err(CalendarError::ImpossibleResult),
218
                }
219
            } else {
220
180k
                let 
p345.7k
:
f6445.7k
=
TermNum::product45.7k
(|j| b[
j134k
], |j| j < n,
k45.7k
).
as_45.7k
();
221
45.7k
                let q3 = x * p3;
222
45.7k
                let m = q3.modulus(b[n - 1].as_());
223
45.7k
                if m.approx_eq(b[n - 1].as_()) || m.approx_eq(0.0) {
224
27.6k
                    a[i] = Self::zero();
225
27.6k
                } else if 
m18.1k
.
fract18.1k
().
approx_eq18.1k
(1.0) {
226
74
                    a[i] = match Self::from_f64(m.ceil()) {
227
74
                        Some(m3) => m3,
228
0
                        None => return Err(CalendarError::ImpossibleResult),
229
                    };
230
                } else {
231
18.0k
                    a[i] = match Self::from_f64(m) {
232
18.0k
                        Some(m3) => m3,
233
0
                        None => return Err(CalendarError::ImpossibleResult),
234
                    };
235
                }
236
            }
237
        }
238
45.7k
        Ok(())
239
45.7k
    }
240
241
2
    fn search_min(p: impl Fn(Self) -> bool, k: Self) -> Self {
242
        //LISTING 1.32 (*Calendrical Calculations: The Ultimate Edition* by Reingold & Dershowitz.)
243
        //Modified to use iteration instead of recursion
244
2
        let mut i = k;
245
13
        while !p(i) {
246
11
            i += Self::one()
247
        }
248
2
        i
249
2
    }
250
251
2
    fn search_max(p: impl Fn(Self) -> bool, k: Self) -> Self {
252
        //LISTING 1.33 (*Calendrical Calculations: The Ultimate Edition* by Reingold & Dershowitz.)
253
        //Modified to use iteration instead of recursion
254
2
        let mut i = k - Self::one();
255
13
        while p(i) {
256
11
            i += Self::one()
257
        }
258
2
        i
259
2
    }
260
}
261
262
impl TermNum for usize {}
263
impl TermNum for u64 {}
264
impl TermNum for u32 {}
265
impl TermNum for u16 {}
266
impl TermNum for u8 {}
267
268
impl TermNum for i64 {
269
256
    fn sign(self) -> Self {
270
256
        if self.is_zero() {
271
0
            Self::zero()
272
        } else {
273
256
            self.signum()
274
        }
275
256
    }
276
277
2.20M
    fn modulus(self, other: Self) -> Self {
278
2.20M
        debug_assert!(other != Self::zero());
279
2.20M
        if other > Self::zero() {
280
2.18M
            let x = self;
281
2.18M
            let y = other;
282
2.18M
            Euclid::rem_euclid(&x, &y)
283
        } else {
284
12.7k
            let x = -self;
285
12.7k
            let y = -other;
286
12.7k
            -Euclid::rem_euclid(&x, &y)
287
        }
288
2.20M
    }
289
}
290
291
macro_rules! CastFn {
292
    ($n: ident, $t:ident) => {
293
256
        fn $n(self) -> Self {
294
256
            (self as $t).$n() as Self
295
256
        }
296
    };
297
    ($n: ident, $t:ident, $u: ident) => {
298
1.66M
        fn $n(self, other: Self) -> $u {
299
0
            (self as $t).$n(other as $t) as $u
300
1.66M
        }
301
    };
302
}
303
304
impl TermNum for i32 {
305
    CastFn!(sign, i64);
306
    CastFn!(modulus, i64, Self);
307
}
308
309
impl TermNum for i16 {
310
    CastFn!(sign, i64);
311
    CastFn!(modulus, i64, Self);
312
}
313
314
impl TermNum for i8 {
315
    CastFn!(sign, i64);
316
    CastFn!(modulus, i64, Self);
317
}
318
319
impl TermNum for f64 {
320
282
    fn sign(self) -> Self {
321
        //LISTING 1.16 (*Calendrical Calculations: The Ultimate Edition* by Reingold & Dershowitz.)
322
        //Modified to use `signum()`
323
282
        if self.is_zero() {
324
2
            Self::zero()
325
        } else {
326
280
            self.signum()
327
        }
328
282
    }
329
330
255k
    fn approx_eq(self, other: Self) -> bool {
331
255k
        let x = self;
332
255k
        let y = other;
333
255k
        if x == y {
334
119k
            true
335
135k
        } else if x.signum() != y.signum() && 
x != 0.00
&&
y != 0.00
{
336
0
            false
337
        } else {
338
135k
            (x - y).abs() < (x.abs() / EQ_SCALE) || 
(x - y).abs() < EFFECTIVE_EPSILON134k
339
        }
340
255k
    }
341
342
135k
    fn approx_floor(self) -> Self {
343
135k
        let x = self;
344
135k
        let cx = x.ceil();
345
135k
        if x.approx_eq(cx) {
346
82.8k
            cx
347
        } else {
348
52.9k
            x.floor()
349
        }
350
135k
    }
351
352
0
    fn floor_round(self) -> Self {
353
        //LISTING 1.15 (*Calendrical Calculations: The Ultimate Edition* by Reingold & Dershowitz.)
354
0
        (self + 0.5).floor()
355
0
    }
356
357
157k
    fn modulus(self, other: Self) -> Self {
358
        //LISTING 1.17 (*Calendrical Calculations: The Ultimate Edition* by Reingold & Dershowitz.)
359
157k
        let x = self;
360
157k
        let y = other;
361
157k
        debug_assert!(y != 0.0); //Cannot replace with NonZero - it's not supported for f64
362
157k
        if x == 0.0 {
363
83.5k
            0.0
364
        } else {
365
74.0k
            x - (y * (x / y).floor())
366
        }
367
157k
    }
368
369
1.63M
    fn is_a_number(self) -> bool {
370
1.63M
        !self.is_nan()
371
1.63M
    }
372
}
373
374
impl TermNum for f32 {
375
    CastFn!(sign, f64);
376
    CastFn!(modulus, f64, Self);
377
    CastFn!(approx_eq, f64, bool);
378
    CastFn!(approx_floor, f64);
379
    CastFn!(floor_round, f64);
380
381
0
    fn is_a_number(self) -> bool {
382
0
        !self.is_nan()
383
0
    }
384
}
385
386
// TODO: binary search (listing 1.35)
387
// TODO: inverse f (listing 1.36)
388
// TODO: list_of_fixed_from_moments (listing 1.37)
389
// TODO: range (1.38)
390
// TODO: scan_range (1.39)
391
// TODO: positions_in_range (1.40)
392
// TODO: angles, minutes, degrees
393
394
#[cfg(test)]
395
mod tests {
396
    use super::*;
397
    use proptest::prop_assume;
398
    use proptest::proptest;
399
400
    #[test]
401
1
    fn modulus_basics() {
402
1
        assert_eq!((9.0).modulus(5.0), 4.0);
403
1
        assert_eq!((-9.0).modulus(5.0), 1.0);
404
1
        assert_eq!((9.0).modulus(-5.0), -1.0);
405
1
        assert_eq!((-9.0).modulus(-5.0), -4.0);
406
1
    }
407
408
    #[test]
409
1
    fn adjusted_remainder() {
410
1
        assert_eq!((13).adjusted_remainder(12), 1);
411
1
    }
412
413
    #[test]
414
    #[should_panic]
415
1
    fn modulus_zero() {
416
1
        (123.0).modulus(0.0);
417
1
    }
418
419
    #[test]
420
1
    fn gcd_wikipedia_examples() {
421
        //See https://en.wikipedia.org/wiki/Greatest_common_divisor
422
1
        assert_eq!(8.0.gcd(12.0), 4.0);
423
1
        assert_eq!(54.0.gcd(24.0), 6.0);
424
1
        assert_eq!(9.0.gcd(28.0), 1.0); //Coprime
425
1
        assert_eq!(24.0.gcd(60.0), 12.0);
426
1
        assert_eq!(42.0.gcd(56.0), 14.0);
427
1
    }
428
429
    #[test]
430
1
    fn lcm_wikipedia_examples() {
431
        //https://en.wikipedia.org/wiki/Least_common_multiple
432
1
        assert_eq!((5.0).lcm(4.0), 20.0);
433
1
        assert_eq!((6.0).lcm(4.0), 12.0);
434
1
    }
435
436
    #[test]
437
1
    fn sum_of_2x() {
438
3
        let 
y1
=
TermNum::sum1
(|x|
x2
* 2.0, |i| i < 3.0, 1.0);
439
1
        assert_eq!(y, 3.0 * 2.0);
440
1
    }
441
442
    #[test]
443
1
    fn product_of_2x() {
444
3
        let 
y1
=
TermNum::product1
(|x|
x2
* 2.0, |i| i < 3.0, 1.0);
445
1
        assert_eq!(y, 2.0 * 4.0);
446
1
    }
447
448
    #[test]
449
1
    fn search_min_sign() {
450
12
        let 
y1
=
f64::search_min1
(|i| i.sign() == 1.0, -10.0);
451
1
        assert_eq!(y, 1.0);
452
1
        let z = f64::search_min(|i| i.sign() == 1.0, 500.0);
453
1
        assert_eq!(z, 500.0);
454
1
    }
455
456
    #[test]
457
1
    fn search_max_sign() {
458
12
        let 
y1
=
f64::search_max1
(|i| i.sign() == -1.0, -10.0);
459
1
        assert_eq!(y, 0.0);
460
1
        let z = f64::search_max(|i| i.sign() == -1.0, 500.0);
461
1
        assert_eq!(z, 499.0);
462
1
    }
463
464
    proptest! {
465
        #[test]
466
        fn mixed_radix_time(ahr in 0..24,amn in 0..59,asc in 0..59) {
467
            let ahr = ahr as f64;
468
            let amn = amn as f64;
469
            let asc = asc as f64;
470
            let a = [ahr, amn, asc];
471
            let b = [60.0, 60.0];
472
            let seconds = TermNum::from_mixed_radix(&a, &b, 2).unwrap();
473
            let minutes = TermNum::from_mixed_radix(&a, &b, 1).unwrap();
474
            let hours = TermNum::from_mixed_radix(&a, &b, 0).unwrap();
475
            let expected_seconds = (ahr * 3600.0) + (amn* 60.0) + asc;
476
            let expected_minutes = (ahr * 60.0) + amn + (asc / 60.0);
477
            let expected_hours = ahr + (amn / 60.0) + (asc / 3600.0);
478
            assert!(seconds.approx_eq(expected_seconds));
479
            assert!(minutes.approx_eq(expected_minutes));
480
            assert!(hours.approx_eq(expected_hours));
481
482
            let mut a_seconds = [0.0, 0.0, 0.0];
483
            let mut a_minutes = [0.0, 0.0, 0.0];
484
            let mut a_hours = [0.0, 0.0, 0.0];
485
            TermNum::to_mixed_radix(seconds, &b, 2, &mut a_seconds).unwrap();
486
            TermNum::to_mixed_radix(minutes, &b, 1, &mut a_minutes).unwrap();
487
            TermNum::to_mixed_radix(hours, &b, 0, &mut a_hours).unwrap();
488
489
            println!("a: {a:?}, a_hours: {a_hours:?}, hours: {hours}");
490
491
            assert!(TermNum::approx_eq_iter(a_seconds, a));
492
            assert!(TermNum::approx_eq_iter(a_minutes, a));
493
            assert!(TermNum::approx_eq_iter(a_hours, a));
494
        }
495
496
        #[test]
497
        fn mixed_radix_time_i(ahr in 0..24,amn in 0..59,asc in 0..59) {
498
            let ahr = ahr as i32;
499
            let amn = amn as i32;
500
            let asc = asc as i32;
501
            let a = [ahr, amn, asc];
502
            let b = [60, 60];
503
            let seconds = TermNum::from_mixed_radix(&a, &b, 2).unwrap();
504
            let minutes = TermNum::from_mixed_radix(&a, &b, 1).unwrap();
505
            let hours = TermNum::from_mixed_radix(&a, &b, 0).unwrap();
506
507
            let ahr = ahr as f64;
508
            let amn = amn as f64;
509
            let asc = asc as f64;
510
            let expected_seconds = (ahr * 3600.0) + (amn* 60.0) + asc;
511
            let expected_minutes = (ahr * 60.0) + amn + (asc / 60.0);
512
            let expected_hours = ahr + (amn / 60.0) + (asc / 3600.0);
513
            assert!(seconds.approx_eq(expected_seconds));
514
            assert!(minutes.approx_eq(expected_minutes));
515
            assert!(hours.approx_eq(expected_hours));
516
517
            let mut a_seconds = [0, 0, 0];
518
            let mut a_minutes = [0, 0, 0];
519
            let mut a_hours = [0, 0, 0];
520
            TermNum::to_mixed_radix(seconds, &b, 2, &mut a_seconds).unwrap();
521
            TermNum::to_mixed_radix(minutes, &b, 1, &mut a_minutes).unwrap();
522
            TermNum::to_mixed_radix(hours, &b, 0, &mut a_hours).unwrap();
523
524
            println!("a: {a:?}, a_hours: {a_hours:?}, hours: {hours}");
525
526
            assert_eq!(&a_seconds, &a);
527
            assert_eq!(&a_minutes, &a);
528
            assert_eq!(&a_hours, &a);
529
        }
530
531
532
        #[test]
533
        fn modulus_positivity(x in EFFECTIVE_MIN..EFFECTIVE_MAX, y in 0.0..EFFECTIVE_MAX) {
534
            assert!((x as f64).modulus(y as f64) >= 0.0);
535
        }
536
537
        #[test]
538
        fn modulus_i_positivity(x: i32, y in 1..i32::MAX) {
539
            assert!(x.modulus(y) >= 0);
540
        }
541
542
543
        #[test]
544
        fn modulus_negative_x(x in 0.0..EFFECTIVE_MAX, y in 0.0..EFFECTIVE_MAX) {
545
            prop_assume!(y != 0.0);
546
            prop_assume!(x.modulus(y) != 0.0);
547
            let a0 = (-x).modulus(y);
548
            let a1 = y - x.modulus(y);
549
            assert!(a0.approx_eq(a1));
550
        }
551
552
        #[test]
553
        fn modulus_i_negative_x(x in 0..i32::MAX, y in 1..i32::MAX) {
554
            prop_assume!(x.modulus(y) != 0);
555
            let a0 = (-x).modulus(y);
556
            let a1 = y - x.modulus(y);
557
            assert_eq!(a0, a1);
558
        }
559
560
        #[test]
561
        fn modulus_mult(
562
            x in -131072.0..131072.0,
563
            y in -131072.0..131072.0,
564
            z in -131072.0..131072.0) {
565
            //LISTING 1.19 (*Calendrical Calculations: The Ultimate Edition* by Reingold & Dershowitz.)
566
            //Using sqrt(EFFECTIVE_MAX) as limit
567
            let x = x as f64;
568
            let y = y as f64;
569
            let z = z as f64;
570
            prop_assume!(y != 0.0);
571
            prop_assume!(z != 0.0);
572
            let a: f64 = x.modulus(y);
573
            let az: f64 = (x*z).modulus(y*z);
574
            println!("x={}; y={}; z={}; a={}; a*z= {}; az= {};", x, y, z, a, a*z, az);
575
            assert!((a * z).approx_eq(az));
576
        }
577
578
        #[test]
579
        fn modulus_i_mult(
580
            x in i16::MIN..i16::MAX,
581
            y in i16::MIN..i16::MAX,
582
            z in i16::MIN..i16::MAX) {
583
            //LISTING 1.19 (*Calendrical Calculations: The Ultimate Edition* by Reingold & Dershowitz.)
584
            let x = x as i32;
585
            let y = y as i32;
586
            let z = z as i32;
587
            prop_assume!(y != 0);
588
            prop_assume!(z != 0);
589
            let a = x.modulus(y);
590
            let az = (x*z).modulus(y*z);
591
            println!("x={}; y={}; z={}; a={}; a*z= {}; az= {};", x, y, z, a, a*z, az);
592
            assert_eq!(a * z, az);
593
        }
594
595
        #[test]
596
        fn modulus_mult_minus_1(x in 0.0..EFFECTIVE_MAX, y in 0.0..EFFECTIVE_MAX) {
597
            //LISTING 1.19 (*Calendrical Calculations: The Ultimate Edition* by Reingold & Dershowitz.)
598
            prop_assume!(y != 0.0);
599
            let a0 = (-x).modulus(-y);
600
            let a1 = -(x.modulus(y));
601
            assert_eq!(a0, a1);
602
        }
603
604
        #[test]
605
        fn modulus_i_mult_minus_1(x in 0..i32::MAX, y in 1..i32::MAX) {
606
            //LISTING 1.19 (*Calendrical Calculations: The Ultimate Edition* by Reingold & Dershowitz.)
607
            let a0 = (-x).modulus(-y);
608
            let a1 = -(x.modulus(y));
609
            assert_eq!(a0, a1);
610
        }
611
612
        #[test]
613
        fn modulus_i_multiple_of_y(x: i32, y: i32) {
614
            //LISTING 1.20 (*Calendrical Calculations: The Ultimate Edition* by Reingold & Dershowitz.)
615
            prop_assume!(y != 0);
616
            let a = (x as i64) - (x.modulus(y) as i64);
617
            assert_eq!(a % (y as i64), 0);
618
        }
619
620
        #[test]
621
        fn modulus_bounds(x in EFFECTIVE_MIN..EFFECTIVE_MAX, y in EFFECTIVE_MIN..EFFECTIVE_MAX) {
622
            //LISTING 1.21 (*Calendrical Calculations: The Ultimate Edition* by Reingold & Dershowitz.)
623
            prop_assume!(y != 0.0);
624
            let a = x.modulus(y) * y.sign();
625
            assert!(0.0 <= a && a < y.abs());
626
        }
627
        #[test]
628
        fn modulus_i_bounds(x: i32, y: i32) {
629
            //LISTING 1.21 (*Calendrical Calculations: The Ultimate Edition* by Reingold & Dershowitz.)
630
            prop_assume!(y != 0);
631
            let a = x.modulus(y) * (y.sign());
632
            assert!(0 <= a && a < y.abs());
633
        }
634
    }
635
}