1#[inline]
8#[must_use]
9pub fn norm360(deg: f64) -> f64 {
10 deg.rem_euclid(360.0)
11}
12
13#[inline]
15#[must_use]
16pub fn norm180(deg: f64) -> f64 {
17 let a = deg.rem_euclid(360.0);
18 if a > 180.0 {
19 a - 360.0
20 } else {
21 a
22 }
23}
24
25#[inline]
28#[must_use]
29#[allow(
30 clippy::cast_possible_truncation,
31 clippy::cast_sign_loss,
32 reason = "floor 后的值恒在 [0,n) 内,截断与符号丢失不会发生"
33)]
34pub fn sector(deg: f64, n: u32) -> u32 {
35 let w = 360.0 / f64::from(n);
36 (norm360(deg) / w).floor() as u32 % n
37}
38
39#[inline]
41#[must_use]
42pub fn within(deg: f64, n: u32) -> f64 {
43 let w = 360.0 / f64::from(n);
44 norm360(deg).rem_euclid(w)
45}
46
47#[inline]
49#[must_use]
50pub fn solar_term_index(sun_longitude: f64) -> u32 {
51 sector(sun_longitude, 24)
52}
53
54#[must_use]
57pub fn unequal_sector(x: f64, span: f64, bounds: &[f64]) -> usize {
58 let v = x.rem_euclid(span);
59 let mut idx = 0;
60 for (i, &b) in bounds.iter().enumerate() {
61 if v >= b {
62 idx = i;
63 } else {
64 break;
65 }
66 }
67 idx
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73
74 #[test]
75 fn norm_ranges() {
76 assert!((norm360(370.0) - 10.0).abs() < 1e-9);
77 assert!((norm360(-10.0) - 350.0).abs() < 1e-9);
78 assert!((norm180(190.0) + 170.0).abs() < 1e-9);
79 assert!((norm180(180.0) - 180.0).abs() < 1e-9);
80 assert!((norm180(-10.0) + 10.0).abs() < 1e-9);
81 }
82
83 #[test]
84 fn zodiac_sectors() {
85 assert_eq!(sector(0.0, 12), 0); assert_eq!(sector(35.0, 12), 1); assert_eq!(sector(359.9, 12), 11); assert!((within(35.0, 12) - 5.0).abs() < 1e-9);
89 }
90
91 #[test]
92 fn solar_terms() {
93 assert_eq!(solar_term_index(0.0), 0); assert_eq!(solar_term_index(90.0), 6); assert_eq!(solar_term_index(315.0), 21); assert_eq!(solar_term_index(360.0), 0); }
98
99 #[test]
100 fn egyptian_terms_of_aries() {
101 let bounds = [0.0, 6.0, 12.0, 20.0, 25.0];
103 assert_eq!(unequal_sector(3.0, 30.0, &bounds), 0); assert_eq!(unequal_sector(15.0, 30.0, &bounds), 2); assert_eq!(unequal_sector(28.0, 30.0, &bounds), 4); }
107
108 use proptest::prelude::*;
109 proptest! {
110 #[test]
111 fn prop_norm360_in_range(d in -1e7f64..1e7) {
112 prop_assert!((0.0..360.0).contains(&norm360(d)));
113 }
114 #[test]
115 fn prop_norm180_in_range(d in -1e7f64..1e7) {
116 let n = norm180(d); prop_assert!(n > -180.0 && n <= 180.0);
118 }
119 #[test]
120 fn prop_sector_in_range(d in -1e7f64..1e7, n in 1u32..360) {
121 prop_assert!(sector(d, n) < n);
122 }
123 #[test]
124 fn prop_within_below_sector_width(d in -1e7f64..1e7, n in 1u32..360) {
125 prop_assert!(within(d, n) < 360.0 / f64::from(n) + 1e-9);
126 }
127 }
128}