diff --git a/src/system.rs b/src/system.rs index 7da3cead..04a7546f 100644 --- a/src/system.rs +++ b/src/system.rs @@ -754,6 +754,25 @@ macro_rules! system { value: self.value.min(other.value), } } + + /// Calculates the Euclidean remainder (least nonnegative remainder) of a quantity + /// modulo `modulus`. The return value should be in the interval [0, |modulus|)`, but + /// rarely, due to rounding error resulting from finite floating point precision, the + /// return value can be `|modulus|`. + #[must_use = "method returns a new number and does not mutate the original value"] + #[inline(always)] + pub fn rem_euclid(self, modulus: Self) -> Self + where + V: $crate::num::Signed, + { + // The Signed trait does not provide rem_euclid, so it must be implemented + let rem = self.value % modulus.value.abs(); + Quantity { + dimension: $crate::lib::marker::PhantomData, + units: $crate::lib::marker::PhantomData, + value: if rem.is_negative() { rem + modulus.value.abs() } else { rem }, + } + } } // Explicitly definte floating point methods for float and complex storage types. diff --git a/src/tests/system.rs b/src/tests/system.rs index 99e48491..7ecd31f9 100644 --- a/src/tests/system.rs +++ b/src/tests/system.rs @@ -401,6 +401,26 @@ mod signed { Test::eq(&Length::new::(-(*l).clone()), &-Length::new::((*l).clone())) } + + #[allow(trivial_casts)] + fn rem_euclid(v: A) -> bool { + let two = V::one() + V::one(); + + // NB: The builtin v.rem_euclid() will sometimes return a finite result when v is + // infinite. The result should be undefined (NaN). + let exp_res = if v.is_infinite() { V::nan() } else { v.rem_euclid(two) }; + + let act_res = Length::new::(*v).rem_euclid(Length::new::(two)).value; + + // Finite precision floating point can result in exp_res being -0.0. This messes up + // test comparison, since the implementation being tested will map a -0.0 to the + // modulus. + if exp_res.is_sign_negative() { + Test::eq(&two, &act_res) + } else { + Test::eq(&exp_res, &act_res) + } + } } } }