orj
orj

Reputation: 13484

Most succinct implementation of a floating point constraint function with wrap-around overflow

I'm looking for the most succinct and general implementation of the following function:

float Constrain(float value, float min, float max);

Where Constrain() bounds value in the range [min, float). Ie, the range includes min but excludes max and values greater than max or less than min wrap around in a circle. Ie, in a similar way to integers over/underflow.

The function should pass the following tests:

Constrain(  0.0,  0.0,  10.0) ==  0.0
Constrain( 10.0,  0.0,  10.0) ==  0.0
Constrain(  5.0,  0.0,  10.0) ==  5.0
Constrain( 15.0,  0.0,  10.0) ==  5.0
Constrain( -1.0,  0.0,  10.0) ==  9.0
Constrain(-15.0,  0.0,  10.0) ==  5.0

Constrain(  0.0, -5.0,   5.0) ==  0.0
Constrain(  5.0, -5.0,   5.0) == -5.0
Constrain(  0.0, -5.0,   5.0) ==  0.0
Constrain( 10.0, -5.0,   5.0) ==  0.0    
Constrain( -6.0, -5.0,   5.0) ==  4.0
Constrain(-10.0, -5.0,   5.0) ==  0.0
Constrain( 24.0, -5.0,   5.0) ==  4.0

Constrain(  0.0, -5.0,   0.0) == -5.0
Constrain(  5.0, -5.0,   0.0) == -5.0
Constrain( 10.0, -5.0,   0.0) == -5.0
Constrain( -3.0, -5.0,   0.0) == -3.0     
Constrain( -6.0, -5.0,   0.0) == -1.0
Constrain(-10.0, -5.0,   0.0) == -5.0

Note that the min param can be assumed to be always numerically less than max.

There is probably a very simple formula to solve this question but and I'm being spectacularly dumb not knowing the generalised solution to it.

Upvotes: 1

Views: 942

Answers (4)

Andrew Tofelt
Andrew Tofelt

Reputation: 300

This also works:

double constrain(double value, double min, double max)
{
    double Range = max - min;

    if (value < min)
        value = max - (max - value ) % (Range + 1); // Range+1 for inclusive

    if (value > max)
        value = (value - min) % (Range) + min; // Range(+0) for exclusive

    return value;
}

Upvotes: 0

EvilEd
EvilEd

Reputation: 11

lrint() may be faster.

inline double fwrap(double x, double y)
{
  return x - y * lrint(x / y - 0.5);
}

double constrain(double x, double lo, double hi)
{
  return fwrap(x - lo, hi - lo) + lo;
}

Upvotes: 1

EvilEd
EvilEd

Reputation: 11

lrint() may be faster.

inline double fwrap(double x, double y)
{
  return x - y * lrint(x / y - 0.5);
}

double constrain(double x, double lo, double hi)
{
  return fwrap(x, hi - lo);
}

Upvotes: 1

Gareth McCaughan
Gareth McCaughan

Reputation: 19981

You're almost looking for the fmod function. fmod(x,y) returns the remainder on dividing x by y, both being doubles. The sign of the result is the same as that of x (equivalently, the corresponding integer-part function is the one that rounds towards zero), and that's why it's only almost what you want. So, if x>=lo then lo+fmod(x-lo,hi-lo) is the Right Thing, but if x<lo then hi+fmod(x-lo,hi-lo) is oh-so-nearly the Right Thing except that when x<lo and the result could be either lo or hi you get hi instead of lo.

So. You can split three ways:

double Constrain(x,lo,hi) {
  double t = fmod(x-lo,hi-lo);
  return t<0 ? t+hi : t+lo;
}

or you can use floor instead [EDITED because the first version of this wasn't what I meant at all]:

double Constrain(x,lo,hi) {
  double t = (x-lo) / (hi-lo);
  return lo + (hi-lo) * (t-floor(t));
}

Take your pick if what you care about is comprehensibility; try them both if what you care about is performance.

Upvotes: 2

Related Questions