Most efficient/elegant way to clip a number?
C++AlgorithmMathLogicC++ Problem Overview
Given a real (n), a maximum value this real can be (upper), and a minimum value this real can be (lower), how can we most efficiently clip n, such that it remains between lower and upper?
Of course, using a bunch of if statements can do this, but that's boring! What about more compact and elegant/fun solutions?
My own quick attempt (C/C++):
float clip( float n, float lower, float upper )
{
n = ( n > lower ) * n + !( n > lower ) * lower;
return ( n < upper ) * n + !( n < upper ) * upper;
}
I'm sure there are other, better ways to do this, that's why I'm putting this out there..!
C++ Solutions
Solution 1 - C++
What about boring, old, readable, and shortest yet:
float clip(float n, float lower, float upper) {
return std::max(lower, std::min(n, upper));
}
?
This expression could also be 'genericized' like so:
template <typename T>
T clip(const T& n, const T& lower, const T& upper) {
return std::max(lower, std::min(n, upper));
}
Update
Billy ONeal added:
> Note that on windows you might have to define NOMINMAX because they define min and max macros which conflict
Solution 2 - C++
Why rewrite something that's already been written for you?
#include <boost/algorithm/clamp.hpp>
boost::algorithm::clamp(n, lower, upper);
As of C++17, this is now part of the STL:
#include <algorithm>
std::clamp(n, lower, upper);
Solution 3 - C++
C++17 is expected to add a clamp function. Courtesy of 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 );
Solution 4 - C++
UPDATE: C++17's <algorithm>
header added std::clamp(value, low, high)
.
In older C++ versions, I'd very rarely go beyond...
return n <= lower ? lower : n >= upper ? upper : n;
...or, if you find it more readable keeping the left-to-right ordering of lower, n and upper...
return n <= lower ? lower : n <= upper ? n : upper;
...or...
return lower >= n ? lower : n <= upper ? n : upper;
(using <=
, >=
is faster than <
, >
because when the terms are equal it avoids further comparisons)
If you know you might have them, you'd want to check if NaN / Inf etc. are preserved....
I say rarely and not never just because sometimes less branching can be faster, but if you or other people you work with are likely to find the code for that cryptic, it's best avoided unless it's in performance-critical code and profiling shows it matters.
Solution 5 - C++
Inelegant, unsafe, costly but branchless:
n= 0.5 * (n + lower + fabs(n - lower));
n= 0.5 * (n + upper - fabs(upper - n));
Solution 6 - C++
the best is clearly
template <typename t>
t clamp2(t x, t min, t max)
{
if (x < min) x = min;
if (x > max) x = max;
return x;
}
as it compiles to
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
it has 0 branches and should be the fastest of all posted above.
also msvc141 with the standard release settings
Solution 7 - C++
You might like the ternary operator:
value = value<lower?lower:value;
value = value>upper?upper:value;
Solution 8 - C++
n = n + ((n < lower) * (lower - n)) + ((n > upper) * (upper - n));
Solution 9 - C++
If you wish to use xtensor, it would support multi-dimensional arrays and the solution would be very elegant.
#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;
//Answer is { 2, 2, -2, -2 }
Solution 10 - C++
The following header file should work for C and C++. Note that it undefines min and max if the macros are already defined:
#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 */
Solution 11 - C++
Clamp a 32-bit float between xmin and xmax without branching:
#include <cstdint>
inline float clipf(float x, float xmin, float xmax)
{
union{float f; int32_t i;} u = {x};
int32_t i;
u.f -= xmin;
i = ~(u.i >> 31);
u.i &= i;
u.f = xmax - xmin - u.f;
i = ~(u.i >> 31);
u.i &= i;
return xmax - u.f;
}
64-bit counterpart:
#include <cstdint>
inline double clip(double x, double xmin, double xmax)
{
union{double f; int64_t i;} u = {x};
int64_t i;
u.f -= xmin;
i = ~(u.i >> 63);
u.i &= i;
u.f = xmax - xmin - u.f;
i = ~(u.i >> 63);
u.i &= i;
return xmax - u.f;
}