views:

223

answers:

8

Hi,

In C#, I often have to limit an integer value to a range of values. For example, if an application expects a percentage, an integer from a user input must not be less than zero or more than one hundred. Another example: if there are five web pages which are accessed through Request.Params["p"], I expect a value from 1 to 5, not 0 or 256 or 99999.

I often end by writing a quite ugly code like:

page = Math.Max(0, Math.Min(2, page));

or even uglier:

percentage =
    (inputPercentage < 0 || inputPercentage > 100) ?
    0 :
    inputPercentage;

Isn't there a smarter way to do such things within .NET Framework?

I know I can write a general method int LimitToRange(int value, int inclusiveMinimum, int inlusiveMaximum) and use it in every project, but maybe there is already a magic method in the framework?

If I need to do it manually, what would be the "best" (ie. less uglier and more fast) way to do what I'm doing in the first example? Something like this?

public int LimitToRange(int value, int inclusiveMinimum, int inlusiveMaximum)
{
    if (value >= inclusiveMinimum)
    {
        if (value <= inlusiveMaximum)
        {
            return value;
        }
        else
        {
            return inlusiveMaximum;
        }
    }
    else
    {
        return inclusiveMinimum;
    }
}
+2  A: 

An alternative way to write your LimitToRange function is as follows.

public int LimitToRange(int value, int inclusiveMinimum, int inclusiveMaximum)
{
    if (value < inclusiveMinimum) { return inclusiveMinimum; }
    if (value > inclusiveMaximum) { return inclusiveMaximum; }
    return value;
}

I think this is a little easier to understand while still being efficient.

Mark Byers
Yes, it's probably easier to read (two if blocks instead of two embedded if-else). I thought using embedded if-else, because I wanted to put the most frequent case (value is in range) first. But maybe the performance gain is so tiny (if all), that it really doesn't matter.
MainMa
@MainMa: If the value is in range, you have to perform two checks, no matter in which order you put your statements. Performance-wise all answers should be equal.
dtb
@dtb: of course. Sorry, my first comment was stupid.
MainMa
+8  A: 

This operation is called 'Clamp' and it's usually written like this:

public static int Clamp( int value, int min, int max )
{
    return (value < min) ? min : (value > max) ? max : value;
}
Trap
That's a weird naming convention you have there...
dtb
I like the name Clamp. Even the Microsoft.Xna.Frameworkhas a Clamp method :)
Syd
+6  A: 

I see Mark's answer and raise it by a this:

public static class InputExtensions
{
    public static int LimitToRange(
        this int value, int inclusiveMinimum, int inclusiveMaximum)
    {
        if (value < inclusiveMinimum) { return inclusiveMinimum; }
        if (value > inclusiveMaximum) { return inclusiveMaximum; }
        return value;
    }
}

Usage:

int userInput = ...;

int result = userInput.LimitToRange(1, 5)

See: Extension Methods

dtb
+1 for using extension methods. I regret not using them more often.
MainMa
Please note that using extension methods does not have to be necessarily a good thing. It's just another programming tool and it has its uses. Put it in the wrong place or context and you'll end up writing unreadable code.
Trap
+2  A: 

No, there isn't any method for that built in the framework. I suppose that it was left out because you already have Min and Max, so you can accomplish it using them.

If you write your own method for it, it doesn't matter much how you write it. If you use if statements or the conditional operator ?, it will still compile to pretty much the same code anyway.

Guffa
A: 

I like Guffa's answer, but I am surprised that no one has yet posted a solution using Min/Max.

public int LimitInclusive(int value, int min, int max)
{
    return Math.Min(max, Math.Max(value, min));
}
Ed Swangren
Maybe using `Min` and `Max` each time would have a performance impact?
MainMa
+5  A: 

A much cleaner method that will work with more than just integers (taken from my own library of shared code):

public static T Clamp<T>(T value, T min, T max) where T : IComparable<T>
{
    if (value.CompareTo(min) < 0)
        return min;
    if (value.CompareTo(max) > 0)
        return max;

    return value;
}
MikeP
That throws a NullReferenceException if `value` is `null`. Better use an IComparer<T> (e.g. `Comparer<T>.Default`) instead. http://msdn.microsoft.com/en-us/library/azhsac5f.aspx
dtb
+2  A: 

To clamp values without giving users any feedback that the value entered by them is wrong, in general, might not be a great idea (IMHO). This might lead to subtle bugs later, which are difficult to debug, especially when min/max values are determined at run time.

Think of this. You have $100 in your bank account, and you want to transfer $150 to your friend. Would you like your banking system to throw an InsufficientFundsException or get into a discussion with your friend that you transferred $150 but he received only $100 (assuming the bank clamped the transfer amount to 100 since your did not have sufficient funds)

That being said, you should also look at code contracts.

public void MyFunction (Type input)
{
   Contract.Requires(input > SomeReferenceValue);
   Contract.Requires (input < SomeOtherReferencValue);

}

This will force the user input to be within the range.

ram
A: 

I like the Clamp name. I would suggest the following class

public class MathHelper
{
    public static int Clamp (int value,int min,int max)
    {
          // todo - implementation 
    }
    public static float Clamp (float value,float min,float max)
    {
          // todo - implementation 
    }
)

or if you want to use generics, then

Note: Using IComparable may cause an exception if the type does not derive from IComparable

public class MathHelper
{
     public static T Clamp<T> (T value, T min, T max)
     {
         // todo - implementation
         T output = value;
         if (((IComparable)value).CompareTo(max) > 0)
         {
             return max;
         }
         if (((IComparable)value).CompareTo(min) < 0)
         {
            return min;
         }
        return  output;
     } 
}
Syd