I want to implement a function conforming to the following interface and contract:
void move_towards(float& value, float target, float step)
// Moves `value` towards `target` by `step`.
// `value` will never go beyond `target`, but can match it.
// If `step == 0.0f`, `value` is unchanged.
// If `step > 0.0f`, `std::abs(target - value)` decreases.
// If `step < 0.0f`, the behavior is undefined.
The idea is to use this function to gradually move an existing floating point value towards another, without ever exceeding the target. This is useful - for example - to perform linear transitions between values as part of the execution of a game loop.
Here's an example test case:
float value = 5.0f;
move_towards(value, 10.f, 1.f); assert(value == 6.0f);
move_towards(value, 10.f, 1.f); assert(value == 7.0f);
move_towards(value, -5.f, 5.f); assert(value == 2.0f);
move_towards(value, -5.f, 5.f); assert(value == -3.0f);
move_towards(value, -5.f, 5.f); assert(value == -5.0f);
move_towards(value, -5.f, 5.f); assert(value == -5.0f);
move_towards(value, 0.f, 15.f); assert(value == 0.0f);
I tried a few branchless ideas using a combination of std::copysign and std::clamp, but they always failed in some edge cases. In the end, I resorted to using a branchy version:
void move_towards(float& value, float target, float step)
{
if (value < target)
{
value += step;
if (value > target)
{
value = target;
}
}
else if (value > target)
{
value -= step;
if (value < target)
{
value = target;
}
}
}
- Is it possible to implement
move_towardsin order to produce branchless instructions? - If not, is it possible to at least minimize the number of branches?
- Regardless, what version provides the best run-time performance?
I think this is the approach that Francois Andrieux was hinting at. Have you tried this? It's just one branch.
https://godbolt.org/z/jxKP6oT1h