Been bashing my head against a mathematical wall trying to implement a Wrapping type that accepts an inclusive upper and lower bound. [1]. I plan on releasing this as a library once I get the base logic functioning.
I have all the generic bits taken care of, but the actual wrapping math is frustrating.
I have found several answers on stackoverflow, but they all seem to either not wrap correctly at all when implemented in rust or act as an exclusive upper range rather than an inclusive range.
Here are some links I have tried and simplified playground examples (just implemented using integers):
This one gets close but fails to wrap the top of the range correctly:
This one fails to wrap either the top or bottom of the range correctly, with the top of the range being an off by one error.
I can't tell if there is something different in the math implementation between C++/C# and rust, or that the algorithms are incorrect.
If the algorithms are incorrect, can someone point me to a correct algorithm for inclusive wrapping, my googlefoo is failing.
By applying the principles of modular arithmetic, you can get this slightly verbose version. As long as you only ever add or subtract integers that are in group 0 and you end up in the proper range, you will have the correct answer.
fn wrap_range(input: i64, max: i64, min: i64) -> i64 {
let modulus = max - min + 1;
let in_group = input % modulus;
let min_group_0 = min - min % modulus;
let mut out = min_group_0 + in_group;
if out < min {
out += modulus;
}
out
}
As far as I can tell, the first version you posted is also producing correct results for your tests¹, but one of your expected values is incorrect— My playground link includes a corrected test and output table showing what I believe the correct outputs should be. The second version appears to be designed for half-open intervals instead of closed ones (based on its arguments to the % operator).
¹ I haven’t tried to follow the logic to see if it will always produce the correct results…
The simplest (but not necessarily most efficient) way to do this is to work with larger integers during the calculation:
fn wrap(x: i64, min: i64, max: i64) -> i64 {
let (x, min, max)=(x as i128, min as i128, max as i128);
(min + (x - min).rem_euclid(max - min + 1)) as i64
}