Soit un réel (n), une valeur maximale que ce réel peut être (supérieur) et une valeur minimale, ce réel peut être (inférieur), comment pouvons-nous découper le plus efficacement n, de sorte qu'il reste entre inférieur et supérieur?
Bien sûr, utiliser un tas de déclarations if peut le faire, mais c'est ennuyeux! Qu'en est-il des solutions plus compactes et élégantes/amusantes?
Ma propre tentative rapide (C/C++):
float clip( float n, float lower, float upper )
{
n = ( n > lower ) * n + !( n > lower ) * lower;
return ( n < upper ) * n + !( n < upper ) * upper;
}
Je suis sûr qu’il existe d’autres, de meilleures façons de le faire, c’est la raison pour laquelle je publie ce message ..!
Qu'en est-il ennuyeux, vieux, lisible et le plus court pour le moment:
float clip(float n, float lower, float upper) {
return std::max(lower, std::min(n, upper));
}
?
Cette expression pourrait aussi être 'générique' comme ceci:
template <typename T>
T clip(const T& n, const T& lower, const T& upper) {
return std::max(lower, std::min(n, upper));
}
Mettre à jour
Billy ONeal a ajouté:
Notez que sous Windows, vous devrez peut-être définir NOMINMAX car ils définissent les macros min et max
Pourquoi réécrire quelque chose qui est déjà écrit pour vous ?
#include <boost/algorithm/clamp.hpp>
boost::algorithm::clamp(n, lower, upper);
Depuis C++ 17, il s’agit maintenant de partie de la STL :
#include <algorithm>
std::clamp(n, lower, upper);
C++ 17 devrait ajouter une fonction clamp . Avec l'aimable autorisation de cppreference.com:
template<class T>
constexpr const T& clamp( const T& v, const T& lo, const T& hi );
template<class T, class Compare>
constexpr const T& clamp( const T& v, const T& lo, const T& hi, Compare comp );
J'irais très rarement au-delà ...
return n <= lower ? lower : n >= upper ? upper : n;
Si vous savez que vous pourriez les avoir, vous voudrez vérifier si NaN/Inf, etc. sont conservés ....
Je dis rarement et jamais jamais parce que parfois moins de ramifications peuvent être plus rapides, mais vous voudriez sûrement le profiler et prouver que cela a aidé et compté ...
Vous pourriez aimer l'opérateur ternaire:
value = value<lower?lower:value;
value = value>upper?upper:value;
Inélégante, dangereuse, coûteuse mais sans succursales:
n= 0.5 * (n + lower + fabs(n - lower));
n= 0.5 * (n + upper - fabs(upper - n));
le meilleur est clairement
template <typename t>
t clamp2(t x, t min, t max)
{
if (x < min) x = min;
if (x > max) x = max;
return x;
}
comme il compile pour
movss xmm0, cs:__real@c2c80000
maxss xmm0, [rsp+38h+var_18]
movss xmm1, cs:__real@42c80000
minss xmm1, xmm0
movss [rsp+38h+var_18], xmm1
il a 0 branches et devrait être le plus rapide de tous les postes mentionnés ci-dessus.
également msvc141 avec les paramètres de version standard
n = n + ((n < lower) * (lower - n)) + ((n > upper) * (upper - n));
Un avertissement à tous ceux qui essaient de faire quelque chose comme cela, où vous vous fixez aux valeurs possibles du type d'entrée.
template<typename T>
T clamp(T input)
{
return boost::algorithm::clamp(input,
std::numeric_limits<T>::min(),
std::numeric_limits<T>::max());
}
Cela échouera pour certaines valeurs de T
et input
. Par exemple:
clamp<int16_t>(32768.0);
reviendra
-32767
Essayez et voyez. Le problème est que input
est converti en T
à l'entrée de la fonction, avant que les comparaisons ne soient effectuées. Et si vous static_cast<int16_t>(+32768)
, vous obtenez UB.
Je n'ai pas de bonne solution, à part le code ci-dessous qui est "meilleur" mais pas complet. Cela fonctionne pour les types entiers petits (int16_t
et int32_t
) et la précision simple float
, mais présente des problèmes avec int64_t
et double
.
template<typename T>
T clamp(double input)
{
double intermediate = return boost::algorithm::clamp<double>(input,
std::numeric_limits<T>::min(),
std::numeric_limits<T>::max());
return boost::numeric_cast<T>(intermediate);
}
Si les performances comptent vraiment pour vous, qu’en est-il d’une solution en ligne qui évite l’attribution lorsque cela n’est pas nécessaire:
#define clip(n, lower, upper) if (n < lower) n= lower; else if (n > upper) n= upper
Si vous souhaitez utiliser xtensor, il prend en charge les tableaux multidimensionnels et la solution est très élégante.
#include <iostream>
#include "xtensor/xarray.hpp"
#include "xtensor/xio.hpp"
#include "xtensor/xview.hpp"
#include "xtensor/xrandom.hpp"
xt::xarray<float> ar({2.1, 2.9, -2.1, -2.9});
std::cout<<xt::cast<int>(xt::trunc(ar))<<std::endl;
// La réponse est {2, 2, -2, -2}
Le fichier d'en-tête suivant devrait fonctionner pour C et C++. Notez que cela indéfinit min et max si les macros sont déjà définies:
#pragma once
#ifdef min
#undef min
#endif
#ifdef max
#undef max
#endif
#ifdef __cplusplus
#include <algorithm>
template <typename T>
T clip(T in, T low, T high)
{
return std::min(std::max(in, low), high);
}
#else /* !__cplusplus */
#define min(a, b) (((a) < (b)) ? (a) : (b))
#define max(a, b) (((a) < (b)) ? (b) : (a))
#define clip(a, b, c) min(max((a), (b)), (c))
#endif /* __cplusplus */