Most efficient/elegant way to clip a number?

C++AlgorithmMathLogic

C++ 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;
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAlex ZView Question on Stackoverflow
Solution 1 - C++justinView Answer on Stackoverflow
Solution 2 - C++RiotView Answer on Stackoverflow
Solution 3 - C++Josh KelleyView Answer on Stackoverflow
Solution 4 - C++Tony DelroyView Answer on Stackoverflow
Solution 5 - C++Yves DaoustView Answer on Stackoverflow
Solution 6 - C++nsnView Answer on Stackoverflow
Solution 7 - C++RichView Answer on Stackoverflow
Solution 8 - C++user997112View Answer on Stackoverflow
Solution 9 - C++Achyut SarmaView Answer on Stackoverflow
Solution 10 - C++WoodyView Answer on Stackoverflow
Solution 11 - C++RyanVView Answer on Stackoverflow