#[inline]
pub fn wrap_pi(x: f64) -> f64 {
use std::f64::consts::{PI, TAU};
let mut y = x % TAU;
if y > PI {
y -= TAU;
} else if y <= -PI {
y += TAU;
}
y
}
#[inline]
pub fn wrap_2pi(x: f64) -> f64 {
use std::f64::consts::TAU;
let y = x % TAU;
if y < 0.0 { y + TAU } else { y }
}
#[inline]
pub fn wrap_180(x: f64) -> f64 {
let mut y = x % 360.0;
if y > 180.0 {
y -= 360.0;
} else if y <= -180.0 {
y += 360.0;
}
y
}
#[inline]
pub fn wrap_360(x: f64) -> f64 {
let y = x % 360.0;
if y < 0.0 { y + 360.0 } else { y }
}
#[cfg(test)]
mod tests {
use super::*;
use std::f64::consts::{PI, TAU};
#[test]
fn wrap_pi_principal_branch() {
assert!((wrap_pi(0.0) - 0.0).abs() < 1e-15);
assert!((wrap_pi(0.5) - 0.5).abs() < 1e-15);
assert!((wrap_pi(-0.5) - (-0.5)).abs() < 1e-15);
}
#[test]
fn wrap_pi_boundary_at_plus_pi() {
assert!((wrap_pi(PI) - PI).abs() < 1e-15);
}
#[test]
fn wrap_pi_boundary_at_minus_pi() {
assert!((wrap_pi(-PI) - PI).abs() < 1e-15);
}
#[test]
fn wrap_pi_multiple_revolutions() {
assert!((wrap_pi(3.0 * PI) - PI).abs() < 1e-12);
assert!((wrap_pi(-3.0 * PI) - PI).abs() < 1e-12);
assert!((wrap_pi(100.0 * TAU + 1.23) - 1.23).abs() < 1e-9);
}
#[test]
fn wrap_pi_typical_residual() {
let model = 0.01;
let obs = TAU - 0.01;
let raw_diff = model - obs;
assert!((wrap_pi(raw_diff) - 0.02).abs() < 1e-12);
}
#[test]
fn wrap_2pi_principal_branch() {
assert!((wrap_2pi(0.0) - 0.0).abs() < 1e-15);
assert!((wrap_2pi(1.0) - 1.0).abs() < 1e-15);
assert!((wrap_2pi(PI) - PI).abs() < 1e-15);
}
#[test]
fn wrap_2pi_boundary_at_2pi() {
assert!(wrap_2pi(TAU).abs() < 1e-15);
}
#[test]
fn wrap_2pi_negative_inputs() {
assert!((wrap_2pi(-PI) - PI).abs() < 1e-15);
assert!((wrap_2pi(-0.1) - (TAU - 0.1)).abs() < 1e-15);
}
#[test]
fn wrap_2pi_multiple_revolutions() {
assert!((wrap_2pi(3.0 * TAU + 0.5) - 0.5).abs() < 1e-9);
assert!((wrap_2pi(-3.0 * TAU + 0.5) - 0.5).abs() < 1e-9);
}
#[test]
fn wrap_180_principal_branch() {
assert!((wrap_180(0.0) - 0.0).abs() < 1e-12);
assert!((wrap_180(90.0) - 90.0).abs() < 1e-12);
assert!((wrap_180(-90.0) - (-90.0)).abs() < 1e-12);
}
#[test]
fn wrap_180_boundary_at_plus_180() {
assert!((wrap_180(180.0) - 180.0).abs() < 1e-12);
}
#[test]
fn wrap_180_boundary_at_minus_180() {
assert!((wrap_180(-180.0) - 180.0).abs() < 1e-12);
}
#[test]
fn wrap_180_typical_ra_residual() {
assert!((wrap_180(359.0 - 1.0) - (-2.0)).abs() < 1e-12);
assert!((wrap_180(1.0 - 359.0) - 2.0).abs() < 1e-12);
}
#[test]
fn wrap_180_multiple_revolutions() {
assert!((wrap_180(720.0) - 0.0).abs() < 1e-12);
assert!((wrap_180(720.0 + 30.0) - 30.0).abs() < 1e-12);
assert!((wrap_180(-720.0 - 30.0) - (-30.0)).abs() < 1e-12);
}
#[test]
fn wrap_360_principal_branch() {
assert!((wrap_360(0.0) - 0.0).abs() < 1e-12);
assert!((wrap_360(180.0) - 180.0).abs() < 1e-12);
}
#[test]
fn wrap_360_boundary_at_360() {
assert!(wrap_360(360.0).abs() < 1e-12);
}
#[test]
fn wrap_360_negative_inputs() {
assert!((wrap_360(-90.0) - 270.0).abs() < 1e-12);
assert!((wrap_360(-180.0) - 180.0).abs() < 1e-12);
assert!((wrap_360(-360.0) - 0.0).abs() < 1e-12);
}
#[test]
fn wrap_360_multiple_revolutions() {
assert!((wrap_360(1080.5) - 0.5).abs() < 1e-9);
assert!((wrap_360(-1080.5) - 359.5).abs() < 1e-9);
}
#[test]
fn wrap_pi_consistent_with_wrap_180() {
for &x_deg in &[-720.0_f64, -180.5, -30.0, 0.0, 45.0, 179.999, 359.0] {
let x_rad = x_deg.to_radians();
let wrapped_rad = wrap_pi(x_rad);
let wrapped_deg_via_180 = wrap_180(x_deg);
let diff = (wrapped_rad - wrapped_deg_via_180.to_radians()).abs();
assert!(
diff < 1e-12,
"x_deg={x_deg}: rad path={wrapped_rad} vs deg path={wrapped_deg_via_180}"
);
}
}
#[test]
fn wrap_2pi_consistent_with_wrap_360() {
for &x_deg in &[-720.0_f64, -180.5, -30.0, 0.0, 45.0, 179.999, 359.0, 1080.5] {
let x_rad = x_deg.to_radians();
let wrapped_rad = wrap_2pi(x_rad);
let wrapped_deg = wrap_360(x_deg);
let diff = (wrapped_rad - wrapped_deg.to_radians()).abs();
assert!(
diff < 1e-12,
"x_deg={x_deg}: rad path={wrapped_rad} vs deg path={wrapped_deg}"
);
}
}
}