Самый эффективный / элегантный способ обрезать номер? - PullRequest
42 голосов
/ 17 февраля 2012

Учитывая реальное (n), максимальное значение этого действительного значения может быть (верхнее), а минимальное значение этого действительного значения может быть (нижнее), как мы можем наиболее эффективно обрезать n, чтобы оно оставалось между нижним и верхним?

Конечно, можно использовать кучу операторов if, но это скучно! А как насчет более компактных и элегантных / забавных решений?

Моя собственная быстрая попытка (C / C ++):

float clip( float n, float lower, float upper )
{
    n = ( n > lower ) * n + !( n > lower ) * lower;
    return ( n < upper ) * n + !( n < upper ) * upper;
}

Я уверен, что есть и другие, лучшие способы сделать это, вот почему я выкладываю это там! ..

Ответы [ 13 ]

76 голосов
/ 17 февраля 2012

Что насчет скучного, старого, читабельного и кратчайшего:

float clip(float n, float lower, float upper) {
  return std::max(lower, std::min(n, upper));
}

Это выражение также можно «обобщить» следующим образом:

template <typename T>
T clip(const T& n, const T& lower, const T& upper) {
  return std::max(lower, std::min(n, upper));
}

Обновление

Billy ONeal добавил:

Обратите внимание, что в окнах вам может потребоваться определить NOMINMAX, поскольку они определяют минимальные и максимальные макросы, конфликтующие

38 голосов
/ 19 сентября 2014

Зачем переписывать что-то, что уже написано для вас ?

#include <boost/algorithm/clamp.hpp>
boost::algorithm::clamp(n, lower, upper);

Начиная с C ++ 17, теперь это часть STL :

#include <algorithm>
std::clamp(n, lower, upper);
19 голосов
/ 16 июня 2016

C ++ 17, как ожидается, добавит функцию зажим .Предоставлено 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 );
16 голосов
/ 17 февраля 2012

ОБНОВЛЕНИЕ : C ++ 17-х <algorithm> добавлен заголовок std::clamp(value, low, high).

В старых версиях C ++ я очень редко выходил за рамки ...

return n <= lower ? lower : n >= upper ? upper : n;

... или, если вы находите его более читабельным, сохраняя порядок слева направо в нижнем, n и верхнем направлениях ...

return n <= lower ? lower : n <= upper ? n : upper;

(использование <= lower лучше, чем < lower, потому что когда n == lower избегает необходимости сравнивать с upper)

Если вы знаете, что они могут быть у вас, вам нужно проверить, сохранились ли NaN / Inf и т. Д. ...

Я говорю редко и не раз, просто потому, что иногда меньше ветвлений может быть быстрее, но вы наверняка захотите профилировать его и доказать, что это помогло и имело значение ...

5 голосов
/ 17 февраля 2012

Вам может понравиться троичный оператор:

value = value<lower?lower:value;
value = value>upper?upper:value;
4 голосов
/ 17 февраля 2012

Не элегантный, небезопасный, дорогой, но не имеющий ответвлений:

n= 0.5 * (n + lower + fabs(n - lower));
n= 0.5 * (n + upper - fabs(upper - n));
2 голосов
/ 22 октября 2017

лучшее - это явно

template <typename t>
t clamp2(t x, t min, t max)
{
if (x < min) x = min;
if (x > max) x = max;
return x;
}

, поскольку оно компилируется в

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

, имеет 0 ветвей и должно быть самым быстрым из всех опубликованных выше.

такжеmsvc141 со стандартными настройками выпуска

1 голос
/ 08 марта 2019

Предупреждение для любого, кто пытается сделать что-то подобное, где вы ограничиваете возможные значения типа ввода.

template<typename T>
T clamp(T input)
{
    return boost::algorithm::clamp(input, 
                                   std::numeric_limits<T>::min(),
                                   std::numeric_limits<T>::max());
}

Это не удастся для некоторых значений T и input.Например:

clamp<int16_t>(32768.0);

вернет

-32767

Попробуйте и посмотрите.Проблема в том, что input приводится к T при входе в функцию, прежде чем будут сделаны сравнения.И если вы static_cast<int16_t>(+32768), вы получите UB.

У меня нет хорошего решения, кроме приведенного ниже кода, который «лучше», но не завершен.Это работает для небольших целочисленных типов (int16_t и int32_t) и одинарной точности float, но имеет проблемы с int64_t и 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 голос
/ 12 июня 2018

Если вы хотите использовать xtensor, он будет поддерживать многомерные массивы, и решение будет очень элегантным.

#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;

// Ответ: {2, 2, -2, -2}

1 голос
/ 10 апреля 2016

Гиперболическая функция тангенса делает это довольно элегантно (часто используется для нейронных сетей). Смотрите код ниже.

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;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...