197

Let's say x, a and b are numbers. I need to limit x to the bounds of the segment [a, b].

In other words, I need a clamp function:

clamp(x) = max( a, min(x, b) )

Can anybody come up with a more readable version of this?

Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
Samuel Rossille
  • 18,940
  • 18
  • 62
  • 90

10 Answers10

247

The way you do it is pretty standard. You can define a utility clamp function:

/**
 * Returns a number whose value is limited to the given range.
 *
 * Example: limit the output of this computation to between 0 and 255
 * (x * 255).clamp(0, 255)
 *
 * @param {Number} min The lower boundary of the output range
 * @param {Number} max The upper boundary of the output range
 * @returns A number in the range [min, max]
 * @type Number
 */
Number.prototype.clamp = function(min, max) {
  return Math.min(Math.max(this, min), max);
};

(Although extending language built-ins is generally frowned upon)

M.A.K. Ripon
  • 2,070
  • 3
  • 29
  • 47
Otto Allmendinger
  • 27,448
  • 7
  • 68
  • 79
  • 59
    Extending native prototypes (`Number.prototype` in this case) is ill-advised for a variety of reasons. See "Bad practice: Extension of native prototypes" at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain – broofa May 09 '18 at 20:08
  • 7
    Developers are not frowned upon for "picking up the slack of the language", but for doing it in a wrong or sloppy way. Extending prototypes [is wrong and should be avoided for a variety of good reasons](https://softwareengineering.stackexchange.com/a/287853), especially when the right way is as easy as using a function. `const clamp = (num, min, max) => Math.min(Math.max(num, min), max)`. – Aurelio Feb 12 '21 at 15:03
  • 11
    Should have extended `Math` and not `Number` – vsync Mar 04 '21 at 16:25
92

a less "Math" oriented approach, but should also work, this way, the < / > test is exposed (maybe more understandable than minimaxing) but it really depends on what you mean by "readable"

function clamp(num, min, max) {
  return num <= min 
    ? min 
    : num >= max 
      ? max 
      : num
}
Yves M.
  • 29,855
  • 23
  • 108
  • 144
dweeves
  • 5,525
  • 22
  • 28
  • 5
    Better to replace < and > with <= and >= in this idiom, so that there's potentially one less comparison done. With that correction, this is definitely my preferred answer: maxes of mins are confusing and error prone. – Don Hatch Jul 13 '16 at 22:21
  • 8
    It's not faster. Math.min/max solution is fastest https://jsperf.com/math-clamp/9 – Manuel Di Iorio Feb 20 '17 at 20:17
  • 1
    @ManuelDiIorio huh? under current version of Chrome, the simple ternary is twice as fast as Math.min/max - and if you remove the overhead from the far more expensive `Math.random()` call, this simple approach is [10 x faster](https://jsperf.com/math-clamp2/1). – mindplay.dk Oct 05 '17 at 08:48
  • This has unexpected behavior in the accidental scenario of the arguments being min > max – Thomas An Nov 02 '17 at 17:34
  • 6
    [Removing the overhead from Math.random() call, Math.min/max solution is fastest](https://jsperf.com/math-clamp/14). This ternary solution is 80% slower. – idbrii Dec 17 '18 at 18:27
  • Wow, what's going on with Safari in these stats. Oddly enough, the ternary solution is much much faster in safari. – bummzack Oct 18 '19 at 14:15
  • Adding some defaults `function clamp(num, min = -Infinity, max = Infinity)` will let you clamp using just a min or max param. for example clamp(num, 0) to clamp to a positive number. – Arye Eidelman Dec 06 '20 at 17:17
  • this better relates to OP, seems more readable/elegant - perhaps named to ' `clampNumber(n, min, max)` '. the OOP approach of extending Number is messy, leaves you to check for null/undefined before invoking (delegate to function). – Lactose.Int.Robot Jun 15 '21 at 15:01
73

There's a proposal to add an addition to the built-in Math object to do this:

Math.clamp(x, lower, upper)

But note that as of today, it's a Stage 1 proposal. Until it gets widely supported (which is not guaranteed), you can use a polyfill.

JKillian
  • 18,061
  • 8
  • 41
  • 74
Tomas Nikodym
  • 13,210
  • 3
  • 19
  • 18
  • 4
    If you want to track this proposal and others, see: [github.com/tc39/proposals](https://github.com/tc39/proposals) – gfullam Nov 20 '18 at 19:18
  • 2
    For those looking, the proposal is listed under [Stage 1 proposals](https://github.com/tc39/proposals/blob/master/stage-1-proposals.md) as "Math Extensions". The actual proposal is here: https://github.com/rwaldron/proposal-math-extensions – WickyNilliams May 20 '20 at 11:38
  • 22
    Still not available in winter 2020 browsers! – gabry Nov 10 '20 at 07:39
  • @gabry - you may use the [Babel Math polyfill](https://babeljs.io/docs/en/babel-preset-env) if you're project uses *Babel* – vsync Jan 19 '21 at 16:28
56

A simple way would be to use

Math.max(min, Math.min(number, max));

and you can obviously define a function that wraps this:

function clamp(number, min, max) {
  return Math.max(min, Math.min(number, max));
}

Originally this answer also added the function above to the global Math object, but that's a relic from a bygone era so it has been removed (thanks @Aurelio for the suggestion)

CAFxX
  • 28,060
  • 6
  • 41
  • 66
  • 2
    This solution is actually way faster, although I consider extending the `prototype` very elegant when it comes to actually use the function. – MaxArt Jul 10 '12 at 09:10
  • 1
    This is an answer from 2012, so seeing the implementation extending prototype is somewhat understandable, BUT it has to be noted it's now widely [considered a bad practice](https://softwareengineering.stackexchange.com/a/287853). The `Math.max(min, Math.min(number, max))` (the relevant bit) is still valid and worth an upvote, so @CAFxX would you consider editing the answer and avoiding the prototype mutation? I think some upvotes you would get are diverted to [this answer](https://stackoverflow.com/a/48813323/1446845) – Aurelio Feb 12 '21 at 14:58
  • Personally I prefer that, so values follow each other: `Math.min(Math.max(MIN, VALUE), MAX)`, and it's easier to remember: `min+max` functions, and `min value max` arguments. – Alex Yaroshevich Aug 27 '23 at 14:24
18

This does not want to be a "just-use-a-library" answer but just in case you're using Lodash you can use .clamp:

_.clamp(yourInput, lowerBound, upperBound);

So that:

_.clamp(22, -10, 10); // => 10

Here is its implementation, taken from Lodash source:

/**
 * The base implementation of `_.clamp` which doesn't coerce arguments.
 *
 * @private
 * @param {number} number The number to clamp.
 * @param {number} [lower] The lower bound.
 * @param {number} upper The upper bound.
 * @returns {number} Returns the clamped number.
 */
function baseClamp(number, lower, upper) {
  if (number === number) {
    if (upper !== undefined) {
      number = number <= upper ? number : upper;
    }
    if (lower !== undefined) {
      number = number >= lower ? number : lower;
    }
  }
  return number;
}

Also, it's worth noting that Lodash makes single methods available as standalone modules, so in case you need only this method, you can install it without the rest of the library:

npm i --save lodash.clamp
Aurelio
  • 24,702
  • 9
  • 60
  • 63
  • 8
    What is the purpose of the comparison `number === number`? Surely it always evaluates to `true`? – Jivan Pal Sep 13 '20 at 13:08
  • 7
    @JivanPal the only value in JS where `number === number` is false is `NaN`, and that is a case you usually want to handle when writing number logic. `Object.is(number, number)` always returns true. – Rafi Dec 20 '20 at 08:39
  • 2
    @Rafi, thanks — JavaScript never ceases to amaze me with its idiosyncrasies compared to other languages. – Jivan Pal Dec 21 '20 at 15:33
  • 2
    Why not just check `isNaN` instead of using `===` lol – Justin Feb 10 '23 at 02:45
  • 1
    Value to value comparison allows to detect NaN in multiple programming languages without any performance cost. – Alexander Shostak Apr 23 '23 at 03:04
  • @JivanPal NaN and its behavior is not a feature specific to JS, it's a general feature of IEEE754 floating-point arithmetic, which is used almost everywhere, including the major CPU architectures. – Ruslan Jul 28 '23 at 12:19
  • @Ruslan Hmm, thanks. This got me wondering, "how does NaN work in C?", and landed me here: https://stackoverflow.com/a/1923933 – Jivan Pal Jul 29 '23 at 17:06
10

If you are able to use es6 arrow functions, you could also use a partial application approach:

const clamp = (min, max) => (value) =>
    value < min ? min : value > max ? max : value;

clamp(2, 9)(8); // 8
clamp(2, 9)(1); // 2
clamp(2, 9)(10); // 9

or

const clamp2to9 = clamp(2, 9);
clamp2to9(8); // 8
clamp2to9(1); // 2
clamp2to9(10); // 9
bingles
  • 11,582
  • 10
  • 82
  • 93
  • 2
    Creation of a function every time you want to clamp a number looks very inefficient – George Apr 20 '20 at 14:04
  • 2
    Probably depends on your use case. This is just a factory function to create the clamp with a set range. You could create once and reuse throughout your app whatever. doesn't have to be called multiple times. Also may not be the tool for all use cases where you don't need to lock in a set range. – bingles Apr 20 '20 at 18:11
  • 1
    @George if you doesn't need to keep the range, just remove the inner arrow function and move the value parameter to the outer arrow function – Alynva May 08 '20 at 17:16
7

If you don’t want to define any function, writing it like Math.min(Math.max(x, a), b) isn’t that bad.

Ricardo
  • 180
  • 2
  • 12
4

This expands the ternary option into if/else which minified is equivalent to the ternary option but doesn't sacrifice readability.

const clamp = (value, min, max) => {
  if (value < min) return min;
  if (value > max) return max;
  return value;
}

Minifies to 35b (or 43b if using function):

const clamp=(c,a,l)=>c<a?a:c>l?l:c;

Also, depending on what perf tooling or browser you use you get various outcomes of whether the Math based implementation or ternary based implementation is faster. In the case of roughly the same performance, I would opt for readability.

Michael Stramel
  • 1,337
  • 1
  • 16
  • 18
1

In the spirit of arrow sexiness, you could create a micro clamp/constrain/gate/&c. function using rest parameters

var clamp = (...v) => v.sort((a,b) => a-b)[1];

Then just pass in three values

clamp(100,-3,someVar);

That is, again, if by sexy, you mean 'short'

stoke motor
  • 101
  • 2
  • 2
    you shouldn't use arrays and sorting for a `clamp`, perf will take a serious hit if you have complex logic (even if it is "sexy") – Andrew McOlash Nov 12 '19 at 18:22
-8

My favorite:

[min,x,max].sort()[1]
user947856
  • 45
  • 1
  • 21
    Downvoting because it doesn't work. `[1,5,19].sort()[1]` returns 19. It could be fixed like this: `[min,x,max].sort(function(a, b) { return a - b; })[1]` but this isn't sexy anymore. Beside when heavily used it may be a performance issue to create a new array just to compare three numbers. – kayahr Mar 14 '16 at 15:50
  • 3
    IMHO this is far more readable than any of the other options, especially with arrow functions: `[min, x, max].sort((a,b) => a-b)[1]` – zaius Jul 28 '16 at 17:27
  • 7
    The reason why this doesn't always work is because the sort method does not compare numerical values properly. (It treats them like strings) – John Leuenhagen May 02 '18 at 04:05
  • @JohnLeuenhagen the what now?!? – Sreenikethan I Apr 30 '21 at 20:27
  • @SreenikethanI the sort method. The method named sort. The thing that kayahr is calling in their example. – John Leuenhagen May 01 '21 at 04:27
  • @JohnLeuenhagen yes yes i did get what you meant lol I was just surprised to know just now that sort does string comparison (haven't personally used sort a lot in javascript) – Sreenikethan I May 01 '21 at 09:55
  • 1
    @SreenikethanI yeah, unfortunately, JavaScript has quite a few nasty "gotchas" like this. JavaScript programmers just have to know about them and work around them. – John Leuenhagen May 02 '21 at 08:02