63

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
  • 7,295
  • 3
  • 19
  • 11

5 Answers5

120
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
  • 22,486
  • 6
  • 42
  • 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
82

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,575
  • 13
  • 9
  • 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
57

If you want to be cute, you can do:

n = sorted([minN, n, maxN])[1]
Steve Howard
  • 6,737
  • 1
  • 26
  • 37
  • 1
    This will require more comparisons than the other approaches. – Platinum Azure May 13 '11 at 20:41
  • 19
    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
  • no points for readability though :) – Michael Mar 27 '23 at 13:39
  • readability: nested min-max needs more care that the parameters are in the right order, there's much less that can go wrong here. if you are reading for what the programmer intended maybe minmax is easier to read, but if you're reading for how the code functions I like this one. – Jasen Mar 29 '23 at 22:35
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
  • 45,269
  • 12
  • 110
  • 134
  • 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
  • 20,101
  • 7
  • 33
  • 54