[Feature request] Add a `wrap` method, similar to `clamp`

Similar to how x.clamp(min, max) will put x into the range min..=max by clamping it within bounds, there should be an x.wrap(min, max) to put x into the range min..=max by wrapping it within bounds:

x.wrap(min, max) == (x - min).rem_euclid(max - min) + min

This operation comes up a lot, and currently requires an inlined implementation each time -- often incorrectly using % instead of rem_euclid, which fails to put x into the range when x < min.

3 Likes

Does it? May be good to specify in what domain(s), I have almost never run across it.

1 Like

I can't speak for every domain, but it's common in game development / creative coding for example, especially with x representing time.

3 Likes

As someone with domain experience here — this is a frustratingly complicated application. Game simulation mostly deals in small, incremental changes and is highly performance sensitive, so it can often times be desirable to replace the division (modulo) with incremental summation in a loop to bring the value back into range (especially if you know it'll only ever exceed the bounds in one of the directions) since the loop will be taken usually zero times, occasionally once, and exceedingly rarely more than once.

clamp only has the (approximately[1]) one implementation. wrap having multiple valid implementation approaches (and having users that care about that choice) makes it harder to provide a universal implementation. The inability to satisfy conflicting constraints is a key reason why lerp was removed from nightly, and that's a significantly more commonly desired operation than wrap ime (biased as it likely is).

If you're using a math crate, it likely provides an extension trait for method syntax lerp. If you have that, the same library can easily provide wrap as well if it is a commonly used routine.


  1. Different spellings do have slightly different behavior w.r.t. NaN propagation. But here the performance difference between them is small enough to rationalize using the most mathematically correct spelling, and it's not like all FPUs even agree on what the "optimal" NaN propagation behavior is. ↩ī¸Ž

8 Likes

Inlined? If someone uses it a lot, surely they should just make it a reusable function (or extension method) in a support library?

Sure, it doesn't require an inlined implementation, but it often ends up as one anyways. You don't necessarily want to go and implement wrap as a re-usable function for every primitive just to replace a one-liner in the moment; and if you're contributing to someone else's project, there may not be an obvious good place to put it.

If it comes up many times within a single project then sure, you should write a function.