Подтвердить что ты не робот

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

При действительном (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;
}

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

4b9b3361

Ответ 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