What's the most elegant way to cap a number to a segment?
Asked Answered
F

10

209

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?

Funnyman answered 10/7, 2012 at 8:58 Comment(1)
no this is bestGrossman
E
254

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)

Eating answered 10/7, 2012 at 9:2 Comment(3)
Extending native prototypes (Number.prototype in this case) is ill-advised for a variety of reasons. See "Bad practice: Extension of native prototypes" at developer.mozilla.org/en-US/docs/Web/JavaScript/…Loggerhead
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, especially when the right way is as easy as using a function. const clamp = (num, min, max) => Math.min(Math.max(num, min), max).Dope
Should have extended Math and not NumberBrazell
P
93

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
}
Phyllome answered 10/7, 2012 at 9:10 Comment(8)
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.Fumble
It's not faster. Math.min/max solution is fastest jsperf.com/math-clamp/9Dkl
@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.Travis
This has unexpected behavior in the accidental scenario of the arguments being min > maxObumbrate
Removing the overhead from Math.random() call, Math.min/max solution is fastest. This ternary solution is 80% slower.Abaddon
Wow, what's going on with Safari in these stats. Oddly enough, the ternary solution is much much faster in safari.Geomorphic
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.Lehrer
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).Undress
B
76

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.

Bandaranaike answered 13/9, 2016 at 19:50 Comment(5)
If you want to track this proposal and others, see: github.com/tc39/proposalsAinu
For those looking, the proposal is listed under Stage 1 proposals as "Math Extensions". The actual proposal is here: github.com/rwaldron/proposal-math-extensionsLithographer
Still not available in winter 2020 browsers!Bekah
@Bekah - you may use the Babel Math polyfill if you're project uses BabelBrazell
It's 8 years later, this function never made it into spec, yet this answer has 76 upvotes. Amazing.Nutbrown
P
69

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)

Phalarope answered 10/7, 2012 at 9:3 Comment(3)
This solution is actually way faster, although I consider extending the prototype very elegant when it comes to actually use the function.Bernete
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. The Math.max(min, Math.min(number, max)) (the relevant bit) is still valid and worth an upvote, so @Phalarope would you consider editing the answer and avoiding the prototype mutation? I think some upvotes you would get are diverted to this answerDope
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.Maxinemaxiskirt
D
19

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
Dope answered 3/11, 2016 at 17:36 Comment(7)
What is the purpose of the comparison number === number? Surely it always evaluates to true?Shaneka
@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.Nessus
@Rafi, thanks — JavaScript never ceases to amaze me with its idiosyncrasies compared to other languages.Shaneka
Why not just check isNaN instead of using === lolJenks
Value to value comparison allows to detect NaN in multiple programming languages without any performance cost.Bently
@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.Antiphony
@Antiphony Hmm, thanks. This got me wondering, "how does NaN work in C?", and landed me here: stackoverflow.com/a/1923933Shaneka
I
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
Inference answered 2/11, 2017 at 20:46 Comment(3)
Creation of a function every time you want to clamp a number looks very inefficientYeomanly
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.Inference
@Yeomanly if you doesn't need to keep the range, just remove the inner arrow function and move the value parameter to the outer arrow functionDissolute
N
6

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

Nickynico answered 15/2, 2018 at 17:44 Comment(1)
User @Phalarope (above) answered the same 6 years before youBrazell
A
5

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.

Arletha answered 21/7, 2020 at 4:54 Comment(0)
M
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'

Militarize answered 26/4, 2017 at 18:15 Comment(1)
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")Permission
P
-8

My favorite:

[min,x,max].sort()[1]
Posner answered 4/3, 2016 at 19:15 Comment(7)
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.Rubiaceous
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]Nonu
The reason why this doesn't always work is because the sort method does not compare numerical values properly. (It treats them like strings)Overman
@JohnLeuenhagen the what now?!?Nystagmus
@SreenikethanI the sort method. The method named sort. The thing that kayahr is calling in their example.Overman
@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)Nystagmus
@SreenikethanI yeah, unfortunately, JavaScript has quite a few nasty "gotchas" like this. JavaScript programmers just have to know about them and work around them.Overman

© 2022 - 2024 — McMap. All rights reserved.