rstmt_traits/ops/
pymod.rs

1/*
2    Appellation: num <module>
3    Contrib: @FL03
4*/
5use crate::OrderedNum;
6use num_traits::{FromPrimitive, Zero};
7
8/// a functional implementation of python's modulo operator
9fn _pymod<A, B, C>(lhs: A, rhs: B) -> C
10where
11    B: PartialOrd + Zero,
12    C: PartialOrd + Zero + core::ops::Add<B, Output = C>,
13    for<'a> A: core::ops::Rem<&'a B, Output = C>,
14{
15    let r = lhs % &rhs;
16    if (r < <C>::zero() && rhs > <B>::zero()) || (r > <C>::zero() && rhs < <B>::zero()) {
17        r + rhs
18    } else {
19        r
20    }
21}
22
23/// [`PyMod`] is a modulo operator inspired by Python's `%` operator, which handles negative
24/// values differently than rust's built-in `%` operator. Specifically, the sign of the result
25/// will **always** match the sign of the divisor.
26pub trait PyMod<Rhs = Self> {
27    type Output;
28
29    fn pymod(self, rhs: Rhs) -> Self::Output;
30}
31
32/// The [`PitchMod`] trait is a particular implementation of the [`PyMod`] trait, specifically
33/// binding the caller to a mod 12 space.
34pub trait PitchMod {
35    type Output;
36
37    fn pmod(self) -> Self::Output;
38}
39
40/*
41 ************* Implementations *************
42*/
43impl<A, B, C> PyMod<B> for A
44where
45    B: OrderedNum,
46    C: OrderedNum + core::ops::Add<B, Output = C>,
47    for<'a> A: core::ops::Rem<&'a B, Output = C>,
48{
49    type Output = C;
50
51    fn pymod(self, rhs: B) -> Self::Output {
52        let r = self % &rhs;
53        if (r.is_negative() && rhs.is_positive()) || (r.is_positive() && rhs.is_negative()) {
54            r + rhs
55        } else {
56            r
57        }
58    }
59}
60
61impl<A> PitchMod for A
62where
63    A: PyMod<A, Output = A> + FromPrimitive,
64{
65    type Output = A::Output;
66
67    fn pmod(self) -> Self::Output {
68        self.pymod(A::from_u8(12).unwrap())
69    }
70}