web-dev-qa-db-fra.com

Le moyen le plus efficace/élégant de découper un nombre?

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 ..!

41
Alex Z

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

71
justin

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);
35
Riot

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 );
18
Josh Kelley

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é ...

14
Tony Delroy

Vous pourriez aimer l'opérateur ternaire:

value = value<lower?lower:value;
value = value>upper?upper:value;
5
Rich

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));
4
Yves Daoust

La fonction de tangente hyperbolique le fait d’une manière très élégante (beaucoup utilisée pour les réseaux de neurones). Voir le code ci-dessous.

 Tanh function axed on 0

float clip(float x, float min, float max) {
  return ((max-min)/2)*((exp(x) - exp(-x))/(exp(x) + exp(-x))) + max - (max-min)/2;
}
2
HichameMoriceau

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

1
nsn
n = n + ((n < lower) * (lower - n)) + ((n > upper) * (upper - n));
1
user997112

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);
}
1
Mark Lakata

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
0
Yves Daoust

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}

0
Achyut Sarma

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 */
0
Woody