Самый эффективный/элегантный способ закрепить номер?
При действительном (n) максимальном значении это реальное может быть (верхнее), а минимальное значение этого реального может быть (ниже), как мы можем наиболее эффективно обрезать n, чтобы он оставался между нижним и верхним?
Конечно, использование кучки if-операторов может сделать это, но это скучно! Как насчет более компактных и элегантных/интересных решений?
Моя собственная быстрая попытка (C/С++):
float clip( float n, float lower, float upper )
{
n = ( n > lower ) * n + !( n > lower ) * lower;
return ( n < upper ) * n + !( n < upper ) * upper;
}
Я уверен, что есть другие, более эффективные способы сделать это, поэтому я помещаю это там..!
Ответы
Ответ 1
Что насчет скучного, старого, читаемого и самого короткого:
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 добавил:
Обратите внимание, что в Windows вам может потребоваться определить NOMINMAX, поскольку они определяют минимальные и максимальные макросы, которые конфликтуют
Ответ 2
Зачем переписывать то, что уже написано для тебя?
#include <boost/algorithm/clamp.hpp>
boost::algorithm::clamp(n, lower, upper);
Начиная с С++ 17, теперь это часть STL:
#include <algorithm>
std::clamp(n, lower, upper);
Ответ 3
Ожидается, что С++ 17 добавит функцию clamp. Предоставлено 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 );
Ответ 4
ОБНОВЛЕНИЕ: 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
Вам может понравиться тернарный оператор:
value = value<lower?lower:value;
value = value>upper?upper:value;
Ответ 6
Неэлегантный, небезопасный, дорогостоящий, но безветренный:
n= 0.5 * (n + lower + fabs(n - lower));
n= 0.5 * (n + upper - fabs(upper - n));
Ответ 7
лучше всего ясно
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:[email protected]
maxss xmm0, [rsp+38h+var_18]
movss xmm1, cs:[email protected]
minss xmm1, xmm0
movss [rsp+38h+var_18], xmm1
он имеет 0 ветвей и должен быть самым быстрым из всех опубликованных выше.
также msvc141 со стандартными настройками выпуска
Ответ 8
n = n + ((n < lower) * (lower - n)) + ((n > upper) * (upper - n));
Ответ 9
Гиперболическая касательная функция делает это довольно элегантно (много используется для нейронных сетей). См. Код ниже.
![Функция Tanh по оси 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;
}
Ответ 10
Если вы хотите использовать 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}
Ответ 11
Следующий заголовочный файл должен работать для C и С++. Обратите внимание, что он не определяет min и max, если макросы уже определены:
#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 */
Ответ 12
Предупреждение для тех, кто пытается сделать что-то подобное, где вы ограничиваете возможные значения типа ввода.
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);
}
Ответ 13
Если производительность действительно важна для вас, то о встроенном решении, которое позволяет избежать присвоения, если это не требуется:
#define clip(n, lower, upper) if (n < lower) n= lower; else if (n > upper) n= upper