54

I want to limit a number to be within a certain range. Currently, I am doing the following:

minN = 1
maxN = 10
n = something() #some return value from a function
n = max(minN, n)
n = min(maxN, n)

This keeps it within minN and maxN, but it doesn't look very nice. How could I do it better?

PS: FYI, I am using Python 2.6.

Mantis Toboggan
  • 6,505
  • 3
  • 18
  • 10
  • Possible duplicate of [How to clamp an integer to some range? (in Python)](http://stackoverflow.com/questions/4092528/how-to-clamp-an-integer-to-some-range-in-python) – Tony Sep 29 '16 at 21:14

5 Answers5

93
def clamp(n, minn, maxn):
    return max(min(maxn, n), minn)

or functionally equivalent:

clamp = lambda n, minn, maxn: max(min(maxn, n), minn)

now, you use:

n = clamp(n, 7, 42)

or make it perfectly clear:

n = minn if n < minn else maxn if n > maxn else n

even clearer:

def clamp(n, minn, maxn):
    if n < minn:
        return minn
    elif n > maxn:
        return maxn
    else:
        return n
Adrien Plisson
  • 21,024
  • 4
  • 40
  • 73
  • 3
    `def clamp(n, minn, maxn): return min(max(n, minn), maxn) ` slightly improves readability with arguments in the same order. – Martin Moene Feb 21 '14 at 07:22
  • The fastest solution (at least in my tests on multiple single random values against the more readable contender `np.clip()`). – mirekphd Jun 25 '20 at 16:07
70

Simply use numpy.clip() (doc):

n = np.clip(n, minN, maxN)

It also works for whole arrays:

my_array = np.clip(my_array, minN, maxN)
Björn
  • 1,413
  • 12
  • 7
  • Thanks. Was having a heck of a time trying not to write a custom calculation twice and not wanting to write my own function. :-) – tim.newport Nov 24 '21 at 03:14
52

If you want to be cute, you can do:

n = sorted([minN, n, maxN])[1]
Steve Howard
  • 6,359
  • 1
  • 25
  • 37
  • 1
    This will require more comparisons than the other approaches. – Platinum Azure May 13 '11 at 20:41
  • 17
    That's why I called it "cute" and not "practical." ;) However, it's highly unlikely that the inefficiency of this code will cause a meaningful performance problem in most cases. – Steve Howard May 13 '11 at 23:34
  • 2
    Woah, that really is cute! I also like how it is invariant under interchange of minN and maxN. This is definitely my favorite clamp function. +1 ^_^ – Navin Sep 15 '13 at 05:06
  • 1
    if someone is interested what version works faster: both are fast but `min(max(...)...)` is about 1.4 times faster. Details: `python -m timeit -s "min_n = 10; max_n = 15" "for x in range(30): max(min(x, max_n), min_n)"`:7.28 usec per loop. `python -m timeit -s "min_n = 10; max_n = 15" "for x in range(30): sorted([min_n, x, max_n])[1]"`: 10.2 usec per loop. `"min_n = 1000; max_n = 15000" "for x in range(-15000, 30000): ..."`: 11 msec per loop, `"min_n = 1000; max_n = 15000" "for x in range(-15000, 30000): ..."`: 14.8 msec per loop – imposeren Oct 16 '15 at 19:10
4

Define a class and have a method for setting the value which performs those validations.

Something vaguely like the below:

class BoundedNumber(object):
    def __init__(self, value, min_=1, max_=10):
        self.min_ = min_
        self.max_ = max_
        self.set(value)

    def set(self, newValue):
        self.n = max(self.min_, min(self.max_, newValue))

# usage

bounded = BoundedNumber(something())
bounded.set(someOtherThing())

bounded2 = BoundedNumber(someValue(), min_=8, max_=10)
bounded2.set(5)    # bounded2.n = 8
Platinum Azure
  • 43,544
  • 11
  • 104
  • 132
  • 1
    Well, it's extra development time to create, but it's SO REUSABLE! :-P – Platinum Azure May 13 '11 at 20:01
  • 1
    i am sure it can even be extended to check for invalid input numbers like NaN or +/-inf. – Adrien Plisson May 13 '11 at 20:04
  • Yeah, and of course it could also be configured to have different bounds as well. :-) – Platinum Azure May 13 '11 at 20:08
  • 1
    and it can be plugged into a user interface for automatic input validation ! the possibilities are endless... you definitely should patent such an invention. – Adrien Plisson May 13 '11 at 20:21
  • Thanks. Downvoter: Is it because this doesn't feel very "Pythonic" or do you have an ACTUAL issue with my answer? – Platinum Azure May 13 '11 at 20:41
  • i get an error with that `NameError: global name 'minN' is not defined` python2.7 – tMC May 13 '11 at 20:58
  • a similar class could raise an exception if an operation causes a number to go out of range? similar to the ranged types in Ada that help catch errors https://en.wikipedia.org/wiki/Ada_%28programming_language%29#Data_types – endolith Aug 08 '14 at 16:12
  • I like this for more complicated examples, e.g. vectors or co-ordinates in a layout system `SpritePosition = BoundVector(max=(screenwidth,screenheight))` solves a very common use case for me. – JeffUK Dec 29 '21 at 16:39
0

Could you not string together some one-line python conditional statements?

I came across this question when looking for a way to limit pixel values between 0 and 255, and didn't think that using max() and min() was very readable so wrote the following function:

def clamp(x, minn, maxx):
   return x if x > minm and x < maxx else (minn if x < minn else maxx)

I would be interested to see how someone more experienced than me would find this way of clamping a value. I assume it must be less efficient than using min() and max(), but it may be useful for someone looking for a more readable (to me at least) function.

Joe Iddon
  • 19,256
  • 7
  • 31
  • 50