use std::ops::{Rem, Sub, Add, Div, Neg, Mul};
use num::{FromPrimitive, Zero, traits::Euclid};
use hipparchus_mean::Two;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Remainder
{
Euclidean = 0x01,
Symmetry = 0x02,
InvertedSymmetry = 0x03,
}
pub trait Modulo
{
fn smod(self, base: Self) -> Self;
fn umod(self, base: Self) -> Self;
}
impl<T> Modulo for T where
T: Copy + PartialOrd + FromPrimitive + Zero
+ Add<Output=Self> + Sub<Output=Self> + Mul<Output=Self> + Div<Output=Self>
+ Rem<Output=Self> + Euclid + Neg<Output=Self>,
{
fn smod(self, base: Self) -> Self
{
if base > T::zero()
{
let r = self % base;
let h = base.half();
if r >= h
{
r - base
}
else if r < -h
{
r + base
}
else
{
r
}
}
else if base < T::zero()
{
let r = self % base;
let h = base.half();
if r <= h
{
r - base
}
else if r > -h
{
r + base
}
else
{
r
}
}
else
{
self
}
}
fn umod(self, base: Self) -> Self
{
self.rem_euclid(&base)
}
}
#[cfg(test)]
mod tests
{
use super::*;
use rstest::*;
use float_cmp::assert_approx_eq;
#[rstest]
#[case(179.9, 360.0)]
#[case(90.0, 360.0)]
#[case(0.0, 360.0)]
#[case(-90.0, 360.0)]
#[case(-180.0, 360.0)]
#[case(180.0, -360.0)]
#[case(90.0, -360.0)]
#[case(0.0, -360.0)]
#[case(-90.0, -360.0)]
#[case(-179.9, -360.0)]
fn test_smod(#[case] value: f64, #[case] base: f64)
{
assert_approx_eq!(f64, value, value.smod(base));
assert_approx_eq!(f64, value, (value+base).smod(base));
assert_approx_eq!(f64, value, (value-base).smod(base));
}
#[rstest]
#[case(180.0, 360.0, -180.0)]
#[case(-180.0, 360.0, -180.0)]
#[case(180.0, -360.0, 180.0)]
#[case(-180.0, -360.0, 180.0)]
fn test_smod_special(#[case] value: f64, #[case] base: f64, #[case] expected: f64)
{
let actual =value.smod(base);
assert_approx_eq!(f64, expected, actual);
assert_eq!(expected, actual);
}
#[rstest]
#[case(0.0f64, 360.0)]
#[case(-0.0f64, 360.0)]
#[case(0.0f64, -360.0)]
#[case(-0.0f64, -360.0)]
#[case(f64::MAX, 0.0f64)]
#[case(f64::MIN, 0.0f64)]
#[case(f64::MAX, -0.0f64)]
#[case(f64::MIN, -0.0f64)]
#[case(0.0f64, 0.0f64)]
#[case(-0.0f64, 0.0f64)]
#[case(0.0f64, -0.0f64)]
#[case(-0.0f64, -0.0f64)]
fn test_smod_zero(#[case] value: f64, #[case] base: f64)
{
let actual =value.smod(base);
assert_approx_eq!(f64, value, actual);
assert_eq!(value, actual);
assert_eq!(value.to_bits(), actual.to_bits());
}
#[rstest]
#[case(179, 360)]
#[case(90, 360)]
#[case(0, 360)]
#[case(-90, 360)]
#[case(-180, 360)]
#[case(180, -360)]
#[case(90, -360)]
#[case(0, -360)]
#[case(-90, -360)]
#[case(-179, -360)]
fn test_smod_i32(#[case] value: i32, #[case] base: i32)
{
assert_eq!(value, value.smod(base));
assert_eq!(value, (value+base).smod(base));
assert_eq!(value, (value-base).smod(base));
}
#[rstest]
#[case(359.0, 360.0)]
#[case(270.0, 360.0)]
#[case(180.9, 360.0)]
#[case(90.0, 360.0)]
#[case(0.0, 360.0)]
#[case(359.0, -360.0)]
#[case(270.0, -360.0)]
#[case(180.9, -360.0)]
#[case(90.0, -360.0)]
#[case(0.0, -360.0)]
fn test_umod(#[case] value: f64, #[case] base: f64)
{
assert_approx_eq!(f64, value, value.umod(base));
assert_approx_eq!(f64, value, (value+base).umod(base));
assert_approx_eq!(f64, value, (value-base).umod(base));
}
#[rstest]
#[case(359, 360)]
#[case(270, 360)]
#[case(180, 360)]
#[case(90, 360)]
#[case(0, 360)]
#[case(359, -360)]
#[case(270, -360)]
#[case(180, -360)]
#[case(90, -360)]
#[case(0, -360)]
fn test_umod_i32(#[case] value: i32, #[case] base: i32)
{
assert_eq!(value, value.umod(base));
assert_eq!(value, (value+base).umod(base));
assert_eq!(value, (value-base).umod(base));
}
}