Values caught in intervals


Intervals or "ranges" are pretty common in programming, a lot of values are set in an interval of sorts and numeric types themselves are in their own range (int, double, long, short, unsigned, etc.). However, most languages don't support a simple way of declaring a custom interval.

value 1.gif [start, end] or die, or be forgiven

It's rather simple to disable assigning out-of-range values to a variable:

if (value >= start && value <= end) my_range = value;

You can nicely pack the above statement into a property in C#:

double my_range;
double start, end;
double MyRange
{
    get { return my_range; }
    set { if (value >= start && value <= end) my_range = value; }
}

Now you can just write:

MyRange -= 3;

And it will update the variable only if the resulting value is in the [start, end] interval.

The above is perfectly fine if you want to keep it strict (can't drink 3 dL of water if there are only 2 dL left). But sometimes you might want be more forgiving (at least drink as much as it's possible):

if (value < start)
    my_range = start;
else if (value > end)
    my_range = end;
else
    my_range = value;

This will bring the value back to the closest limit when the value is out of range.

fall through a floor / jump through a ceiling

The previous code isn't convenient when you want to loop through an interval by incrementing or decrementing the value and letting it fall/jump through the limit. A typical example that I can think of is a slideshow of photos that automatically loops through the whole set. It gets to n-th photo, then starts again from the beginning.

The obvious way to implement this is simply checking the value if it's too low or high and setting it to the end or start, if it is. But this is nothing more than the "forgiving" code reversed:

if (value < start)
    my_range = end;
else if (value > end)
    my_range = start;
else
    my_range = value;

More precise way to loop through an interval is to add/subtract the rest of the value that was cut-off by the limit.

Suppose you have 1L bottle, 2/10 full, that can be refilled. Drinking 3 dL would result in the bottle being 9/10 full:

2/10 full >> drink 2 dL (0/10 full) >> refill (10/10 full) >> drink 1 dL (9/10 full)

For this to work we need the intervals to repeat themselves infinitely, meaning that where one ends, new one begins and vice-versa for negative infinity.

r: range size
s: range start
n: interval number

Range of integers:

[r * (n - 1) + s, r * n + s - 1] = { n 1.gif Z, r 1.gif N, s 1.gif Z }

Range of real numbers (excluding max)

[r * (n - 1) + s, r * n + s) = { n 1.gif Z, r 1.gif R, r > 0, s 1.gif R }

Range of real numbers (excluding min)

(a, b] form of the above interval

1-256 range of integers:

... -256] [-255, 0] [1, 256] [257, 512] [513 ...

1.5-7.64 range of real numbers (excluding max):

... -4.64) [-4.64, 1.5) [1.5, 7.64) [7.64, 13.78) [13.78 ...

Let's say the current value in both cases is 2 and we subtract 3 from both ranges:

For integer range this would result in going 3 "steps" down:

2 >> 1 >> 256 (0) >> 255 (-1)

Range of real numbers doesn't have steps, because the values are "fuzzy":

2 >> 5.14 (-1)

I'll explain range of real numbers first. To get it to work, we need to find the interval in which our value is and move the value to the original interval. And to get that to work, we need to find n:

n = (value - start) / size

Since n is the number of the interval (original one is 0), it belongs to the set of integers (n 1.gif Z), so the fractional part needs to be removed or "floored".

Subtracting start from the value (value - start) is necessary, value needs to be in [0, size) set of intervals (e.g. [r * (n-1), r * n)) to be divided properly.

Now to move the value to our original interval:

value - n * size

In C# code (implemented inside a property):

double my_range;
double start, size;
double MyRange
{
    get { return my_range; }
    set
    {
        int n = (int)Math.Floor((value - start) / size);
        my_range = value - n * size;
    }
}

This gives us the ability to simply write:

MyRange = 2;
MyRange -= 3;

Which results in 5.14, as expected (from the [1.5, 7.64) interval).

This was interval with upper limit excluded [min, max), for interval with bottom limit excluded (min, max], we need to add another step, a simple check that sets our variable to the end if it's at the start:

int n = (int)Math.Floor((value - start) / size);
value -= n * size;
my_range = value == start ? start + size : value;

Integer range is implemented in the same manner:

int my_range;
int start, size;
int MyRange
{
    get { return my_range; }
    set
    {
        int n = (int)Math.Floor((double)(value - start) / size);
        my_range = value - n * size;
    }
}

The great thing about Math.Floor(number) is that it will floor values such as -0.1 to -1, which is just what we need here and it's what simple integer division doesn't do.

You can now do all sorts of stuff with your variable and it will always stay in your defined interval or range.

Code safely!


Similar Articles