#[inline]
#[must_use]
pub fn norm360(deg: f64) -> f64 {
deg.rem_euclid(360.0)
}
#[inline]
#[must_use]
pub fn norm180(deg: f64) -> f64 {
let a = deg.rem_euclid(360.0);
if a > 180.0 {
a - 360.0
} else {
a
}
}
#[inline]
#[must_use]
#[allow(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
reason = "floor 后的值恒在 [0,n) 内,截断与符号丢失不会发生"
)]
pub fn sector(deg: f64, n: u32) -> u32 {
let w = 360.0 / f64::from(n);
(norm360(deg) / w).floor() as u32 % n
}
#[inline]
#[must_use]
pub fn within(deg: f64, n: u32) -> f64 {
let w = 360.0 / f64::from(n);
norm360(deg).rem_euclid(w)
}
#[inline]
#[must_use]
pub fn solar_term_index(sun_longitude: f64) -> u32 {
sector(sun_longitude, 24)
}
#[must_use]
pub fn unequal_sector(x: f64, span: f64, bounds: &[f64]) -> usize {
let v = x.rem_euclid(span);
let mut idx = 0;
for (i, &b) in bounds.iter().enumerate() {
if v >= b {
idx = i;
} else {
break;
}
}
idx
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn norm_ranges() {
assert!((norm360(370.0) - 10.0).abs() < 1e-9);
assert!((norm360(-10.0) - 350.0).abs() < 1e-9);
assert!((norm180(190.0) + 170.0).abs() < 1e-9);
assert!((norm180(180.0) - 180.0).abs() < 1e-9);
assert!((norm180(-10.0) + 10.0).abs() < 1e-9);
}
#[test]
fn zodiac_sectors() {
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);
}
#[test]
fn solar_terms() {
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); }
#[test]
fn egyptian_terms_of_aries() {
let bounds = [0.0, 6.0, 12.0, 20.0, 25.0];
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); }
use proptest::prelude::*;
proptest! {
#[test]
fn prop_norm360_in_range(d in -1e7f64..1e7) {
prop_assert!((0.0..360.0).contains(&norm360(d)));
}
#[test]
fn prop_norm180_in_range(d in -1e7f64..1e7) {
let n = norm180(d); prop_assert!(n > -180.0 && n <= 180.0);
}
#[test]
fn prop_sector_in_range(d in -1e7f64..1e7, n in 1u32..360) {
prop_assert!(sector(d, n) < n);
}
#[test]
fn prop_within_below_sector_width(d in -1e7f64..1e7, n in 1u32..360) {
prop_assert!(within(d, n) < 360.0 / f64::from(n) + 1e-9);
}
}
}