Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Branchless way to represent a "ping-pong" value?

I need a changing value that can be manually stepped with step() that goes back and forth a min and a max, moving by speed every step().

This is my current code:

template<typename T> struct PingPongValue {
        T value, min, max, speed, dir{1};

        PingPongValue(T mMin, T mMax, T mSpeed) 
           : value(mMin), min(mMin), max(mMax), speed(mSpeed) { }

        void step()
        {
            value += speed * dir;
                 if(value > max) { value = max; dir = -1; }
            else if(value < min) { value = min; dir = +1; }
        }
};

Example:

PingPongValue v{0, 5, 1};
v.step(); // v.value == 1
v.step(); // v.value == 2
v.step(); // v.value == 3
v.step(); // v.value == 4
v.step(); // v.value == 5
v.step(); // v.value == 4
v.step(); // v.value == 3
v.step(); // v.value == 2
// etc...

I suppose there's a mathematical way to represent this as a branchless function, but I cannot figure it out. I tried using modulo but I still need a dir variable to change step direction.

like image 461
Vittorio Romeo Avatar asked Dec 15 '22 06:12

Vittorio Romeo


2 Answers

You can do it with an array, something like this (WARNING: probably has off-by-one errors galore!):

int total_steps = 2*(max - min + 1)/speed; // this may be wrong -- have to double check
T steps[total_steps];
for(int i = 0; i < max - min; ++i)
    steps[total_steps - i] = steps[i] = min + i*speed;

Then you can use modulo total_steps to step through the array forever.

like image 110
Paul Evans Avatar answered Dec 25 '22 19:12

Paul Evans


I'm not a fan of "branchless" algorithms, as many times they're just used "because branching is slow", and in that case, IMO it's the optimizer's job to find a faster way.

Nevertheless, you could use comparisons, as bools yield either 0 or 1 when converted to an integral type. Whether that's branchless depends on the architecture AFAIK.

value += speed*dir;                           // allowing over-/underflow
value += (min-value)*(value<min) + (max-value)*(value>max);  // clamp
dir   += 2* ((value==min) - (value==max));    // set dir

SSCCE:

template<typename T> struct PingPongValue {
        T value, min, max, speed, dir{1};

        PingPongValue(T mMin, T mMax, T mSpeed) 
           : value(mMin), min(mMin), max(mMax), speed(mSpeed) { }

        void step()
        {
            // allowing over-/underflow
            value += speed*dir;

            // clamp
            value += (min-value)*(value<min) + (max-value)*(value>max);

            // set dir
            dir   += 2* ((value==min) - (value==max));
        }
};


#include <iostream>

template<class T>
void step(PingPongValue<T>& v)
{
    v.step();
    std::cout << "stepped to: " << v.value << std::endl;
}

int main()
{
    PingPongValue<int> p{-3, 6, 2};
    std::cout << "initial: " << p.value << std::endl;
    for(int i = 0; i < 10; ++i)
    {
        step(p);
    }
}

Output:

initial: -3
stepped to: -1
stepped to: 1
stepped to: 3
stepped to: 5
stepped to: 6
stepped to: 4
stepped to: 2
stepped to: 0
stepped to: -2
stepped to: -3
like image 27
dyp Avatar answered Dec 25 '22 21:12

dyp