Quelle est la manière la plus élégante de limiter un nombre à un segment?

127

Disons x, aet bsont des nombres. J'ai besoin de plafonner xaux limites du segment [a, b].

Je sais écrire Math.max(a, Math.min(x, b)), mais je ne pense pas que ce soit très facile à lire. Quelqu'un a-t-il une manière intelligente d'écrire ceci d'une manière plus lisible?

Samuel Rossille
la source

Réponses:

197

La façon dont vous le faites est assez standard. Vous pouvez définir une clampfonction utilitaire :

/**
 * 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);
};

(Bien que l'extension du langage intégré soit généralement mal vue)

Otto Allmendinger
la source
OOps vous copiez / collez ma faute de frappe (permuté max et min)
Samuel Rossille
5
Non, je l'ai obtenu sur le site. L'ordre d'imbrication / d'exécution de Math.minet Math.maxn'est pas pertinent tant que le paramètre de Math.minest maxet le paramètre de Math.maxest min.
Otto Allmendinger
23
L'extension des prototypes natifs ( Number.prototypedans ce cas) est déconseillée pour diverses raisons. Voir "Mauvaise pratique: Extension de prototypes natifs" sur developer.mozilla.org/en-US/docs/Web/JavaScript/…
broofa
68

une approche moins orientée "Math", mais qui devrait aussi fonctionner, de cette façon, le test </> est exposé (peut-être plus compréhensible que minimaxing) mais cela dépend vraiment de ce que vous entendez par "lisible"

function clamp(num, min, max) {
  return num <= min ? min : num >= max ? max : num;
}
dweeves
la source
4
Mieux vaut remplacer <et> par <= et> = dans cet idiome, afin qu'il y ait potentiellement une comparaison de moins effectuée. Avec cette correction, c'est certainement ma réponse préférée: un maximum de minutes est déroutant et sujet aux erreurs.
Don Hatch
C'est la meilleure réponse. Beaucoup plus rapide, beaucoup plus simple, beaucoup plus lisible.
tristan
5
Ce n'est pas plus rapide. La solution Math.min / max est la plus rapide jsperf.com/math-clamp/9
Manuel Di Iorio
1
@ManuelDiIorio hein? sous la version actuelle de Chrome, le ternaire simple est deux fois plus rapide que Math.min / max - et si vous supprimez la surcharge de l' Math.random()appel beaucoup plus cher , cette approche simple est 10 fois plus rapide .
mindplay.dk
48

Mise à jour pour ECMAScript 2017:

Math.clamp(x, lower, upper)

Mais notez qu'à partir d'aujourd'hui, c'est une proposition de l' étape 1 . Jusqu'à ce qu'il soit largement pris en charge, vous pouvez utiliser un polyfill .

Tomas Nikodym
la source
4
Si vous souhaitez suivre cette proposition et d'autres, voir: github.com/tc39/proposals
gfullam
5
Je n'ai pas trouvé cette proposition dans le repo tc39 / propositions
Colin D
Pour ceux qui recherchent, la proposition est répertoriée sous les propositions de l' étape 1 comme "Extensions mathématiques". La proposition actuelle est ici: github.com/rwaldron/proposal-math-extensions
WickyNilliams
14
Math.clip = function(number, min, max) {
  return Math.max(min, Math.min(number, max));
}
CAFxX
la source
2
Cette solution est en fait beaucoup plus rapide, même si je considère étendre le prototypetrès élégant lorsqu'il s'agit d'utiliser réellement la fonction.
MaxArt
11

Cela ne veut pas être une réponse "juste utiliser une bibliothèque", mais juste au cas où vous utilisez Lodash, vous pouvez utiliser .clamp:

_.clamp(yourInput, lowerBound, upperBound);

Pour que:

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

Voici sa mise en œuvre, tirée de la source Lodash :

/**
 * 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;
}

En outre, il convient de noter que Lodash rend des méthodes uniques disponibles en tant que modules autonomes, donc si vous n'avez besoin que de cette méthode, vous pouvez simplement l'installer sans le reste de la bibliothèque:

npm i --save lodash.clamp
Nobita
la source
6

Si vous êtes capable d'utiliser les fonctions fléchées es6, vous pouvez également utiliser une approche d'application partielle:

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
la source
La création d'une fonction à chaque fois que vous voulez serrer un nombre semble très inefficace
George
1
Cela dépend probablement de votre cas d'utilisation. Il s'agit simplement d'une fonction d'usine pour créer la pince avec une plage définie. Vous pouvez créer une seule fois et réutiliser dans toute votre application. n'a pas besoin d'être appelé plusieurs fois. Il peut également ne pas être l'outil pour tous les cas d'utilisation où vous n'avez pas besoin de verrouiller une plage définie.
bingles le
@George si vous n'avez pas besoin de conserver la plage, supprimez simplement la fonction de flèche intérieure et déplacez le paramètre de valeur vers la fonction de flèche extérieure
Alisson Nunes
6

Si vous ne voulez définir aucune fonction, l'écrire comme ça Math.min(Math.max(x, a), b)n'est pas si mal.

Ricardo
la source
0

Dans l'esprit de la sensualité des flèches, vous pourriez créer une micro pince / contrainte / porte / & c. fonction utilisant les paramètres de repos

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

Ensuite, passez simplement trois valeurs

clamp(100,-3,someVar);

C'est, encore une fois, si par sexy, tu veux dire `` court ''

moteur de course
la source
vous ne devriez pas utiliser de tableaux et de tri pour un clamp, perf prendra un sérieux coup si vous avez une logique complexe (même si c'est "sexy")
Andrew McOlash
0

Cela étend l'option ternaire en if / else qui minifié équivaut à l'option ternaire mais ne sacrifie pas la lisibilité.

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

Réduit à 35b (ou 43b si vous utilisez function):

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

En outre, en fonction de l'outil de performance ou du navigateur que vous utilisez, vous obtenez divers résultats quant à savoir si l'implémentation basée sur les mathématiques ou l'implémentation basée sur ternaire est plus rapide. Dans le cas de performances à peu près identiques, j'opterais pour la lisibilité.

Michael Stramel
la source
-5

Mon préféré:

[min,x,max].sort()[1]
user947856
la source
12
Downvoter parce que cela ne fonctionne pas. [1,5,19].sort()[1]renvoie 19. Cela pourrait être corrigé comme ceci: [min,x,max].sort(function(a, b) { return a - b; })[1]mais ce n'est plus sexy. En plus d'une utilisation intensive, il peut être un problème de performances de créer un nouveau tableau juste pour comparer trois nombres.
kayahr
5
Donner ce +1 quand même, car c'est sexy qui compte.
Don Hatch
3
À [min, x, max].sort((a,b) => a-b)[1]
mon humble avis,
4
La raison pour laquelle cela ne fonctionne pas toujours est que la méthode de tri ne compare pas correctement les valeurs numériques. (Il les traite comme des cordes)
John Leuenhagen
Je pense qu'il n'y a rien de plus sexy qu'une fonction bien nommée qui fait le travail efficacement et d'une manière évidemment correcte. Mais c'est peut-être une question de goût.
BallpointBen