1#[must_use]
15pub fn norm_360(deg: f64) -> f64 {
16 if !deg.is_finite() {
17 return deg;
18 }
19 let r = deg % 360.0;
20 if r < 0.0 { r + 360.0 } else { r }
21}
22
23#[must_use]
27pub fn norm_180(deg: f64) -> f64 {
28 if !deg.is_finite() {
29 return deg;
30 }
31 let r = norm_360(deg);
32 if r > 180.0 { r - 360.0 } else { r }
33}
34
35#[must_use]
40pub fn diff(from_deg: f64, to_deg: f64) -> f64 {
41 norm_180(from_deg - to_deg)
42}
43
44#[must_use]
46pub fn reciprocal(deg: f64) -> f64 {
47 norm_360(deg + 180.0)
48}
49
50#[cfg(test)]
51mod tests {
52 use super::*;
53
54 #[test]
55 fn norm_360_wraps_both_directions() {
56 assert_eq!(norm_360(0.0), 0.0);
57 assert_eq!(norm_360(359.9), 359.9);
58 assert_eq!(norm_360(360.0), 0.0);
59 assert_eq!(norm_360(370.0), 10.0);
60 assert_eq!(norm_360(-10.0), 350.0);
61 assert_eq!(norm_360(-370.0), 350.0);
62 assert_eq!(norm_360(-720.0), 0.0);
63 }
64
65 #[test]
66 fn norm_180_keeps_180_positive() {
67 assert_eq!(norm_180(180.0), 180.0);
68 assert_eq!(norm_180(-180.0), 180.0);
69 assert_eq!(norm_180(190.0), -170.0);
70 assert_eq!(norm_180(-190.0), 170.0);
71 assert_eq!(norm_180(0.0), 0.0);
72 }
73
74 #[test]
75 fn diff_signs_point_to_starboard() {
76 assert_eq!(diff(10.0, 350.0), 20.0);
77 assert_eq!(diff(350.0, 10.0), -20.0);
78 assert_eq!(diff(90.0, 90.0), 0.0);
79 }
80
81 #[test]
82 fn reciprocal_round_trips() {
83 assert_eq!(reciprocal(0.0), 180.0);
84 assert_eq!(reciprocal(180.0), 0.0);
85 assert!((reciprocal(reciprocal(123.4)) - 123.4).abs() < 1e-9);
89 }
90
91 #[test]
92 fn nan_survives_rather_than_becoming_north() {
93 assert!(norm_360(f64::NAN).is_nan());
94 assert!(norm_180(f64::NAN).is_nan());
95 }
96}