61

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..!

Alex Z
  • 2,500
  • 2
  • 19
  • 23
  • 2
    "cap" usually refers only to an upper limit. The word you want is "clip". – Ignacio Vazquez-Abrams Feb 17 '12 at 06:34
  • 7
    I doubt about efficiency, but your solution really is not readable. Why don't you just define a some kind of "clamp" function and use that. – dbrank0 Feb 17 '12 at 06:42
  • 2
    Also read this related question: http://stackoverflow.com/questions/427477/fastest-way-to-clamp-a-real-fixed-floating-point-value – dbrank0 Feb 17 '12 at 06:56
  • Hmm did a search and missed that :/ Must have been because I was originally using the term cap, not clip/clamp. Thanks – Alex Z Feb 17 '12 at 07:00
  • http://stackoverflow.com/questions/4370474/is-there-a-function-in-c-or-c-to-do-saturation-on-an-integer – phuclv Mar 10 '15 at 18:31
  • The thing that truly baffles me is: what lead you to the complicated bool trickery with 4 `*` operations in place of the more natural `if (nupper) return upper; return n;`? (This firmly reinforces my belief that _programmers are often their own worst enemy when it comes to taking the trivial and making it extremely complicated._) – Disillusioned May 13 '16 at 23:00
  • I think you're missing the point of the question - note that I say "Of course, using a bunch of if statements can do this, but that's boring!". This was a question to dig at possible alternatives to that method. I'm certainly not suggesting that my solution up there is the best, that's for sure! – Alex Z May 16 '16 at 00:19
  • Ok. I took the phrase "bunch of if statements" too literally; I don't consider 2 to be a bunch, and figured you were overcomplicating the if logic to the point where you started contemplating the truly weird. Of course you could always condense 2 ifs into 2 ternaries: `return (nhigher) ? higher : n;` (Personally I don't really like code golf though and put far more weight on easily readable and maintainable.) – Disillusioned May 16 '16 at 08:15
  • Readability and simplicity is why I selected the answer by @justin :) – Alex Z May 21 '16 at 04:13
  • Just out of curiosity, it's not like comparison operators perform better than an if check? – Zebrafish Jan 07 '18 at 09:27
  • related [fastest-way-to-clamp-a-real-fixed-floating-point-value](https://stackoverflow.com/q/427477/52074) – Trevor Boyd Smith May 04 '18 at 15:07

10 Answers10

101

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

justin
  • 104,054
  • 14
  • 179
  • 226
  • 3
    +1 -- good enough that I deleted my answer. Note that on windows you might have to define `NOMINMAX` because they define min and max macros which conflict :( – Billy ONeal Feb 17 '12 at 07:35
  • 5
    This belongs in the standard. C++1z, anyone? – jbruni Feb 04 '14 at 23:18
  • 1
    @jbruni o hay: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4536.html – underscore_d May 29 '16 at 14:39
  • @underscore_d Thanks for the update. I hope it gets approved. – jbruni Jun 20 '16 at 23:46
  • Would the following be a correct implementation? https://pastebin.com/m3yVJCsx I saw this in a library I am using and the comparison seems wrong at least for using floats in this 'genericized' version? – stephanmg Oct 17 '19 at 14:49
  • @BillyONeal – _(and for others to know too)_ – There's another way to prevent this conflict from happening. Just add a set of `()` around the functions call. That is, writing it like that `return (std::max)(lower, (std::min)(n, upper));` will solve the issue without having to define `NOMINMAX`... – Tenphase Aug 22 '21 at 19:22
68

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);
Riot
  • 15,723
  • 4
  • 60
  • 67
  • 29
    standard answer: because not everyone is willing/allowed/physically able to pull in Boost. However, this implementation was suggested for migration to the stdlib, dunno the current status though. – underscore_d May 29 '16 at 14:40
  • 12
    Looks like it will be standard in C++17: http://en.cppreference.com/w/cpp/algorithm/clamp – Josh Kelley Jun 16 '16 at 20:43
  • `clamp` part of Swift standard library too – Warren Burton May 18 '17 at 13:28
  • 1
    @underscore_d don't worry it's part of C++17. until then you can continue constantly reinventing the wheel and avoid using good peer-reviewed portable code. – Trevor Boyd Smith May 04 '18 at 16:02
  • 8
    @TrevorBoydSmith I don't have anything against Boost, and your rather contrived evangelism for it is not needed. That said, it looks like I wasn't wrong in assessing the usual reasons that people don't just do what you want them to do and use Boost, and it's surprisingly not because they just want to annoy you by "avoid[ing] using good peer-reviewed portable code". I also don't see why you edited this existing answer to be completely different when (A) you could've posted your own but also (B) other people already had, but OK. – underscore_d May 05 '18 at 17:18
  • That's a header only library, correct me if I'm wrong, so essentially it is not a big problem because we don't need to compile. – stephanmg Oct 17 '19 at 14:48
  • Adding the entire boost lib for 1 line of code seems a bit overkill. – Marc Dirven Aug 04 '21 at 12:53
  • @MarcDirven you're not adding "the whole boost lib", you're including one header - only the functionality you are using is compiled in, there is no bloat. – Riot Aug 05 '21 at 19:17
  • @Riot Still, installing/downloading a library for such a trivial thing still seems overkill to me imo. – Marc Dirven Aug 06 '21 at 13:22
26

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 );
Josh Kelley
  • 56,064
  • 19
  • 146
  • 246
25

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.

Tony Delroy
  • 102,968
  • 15
  • 177
  • 252
  • "<=, >= is faster than <, >" - probably not. See https://stackoverflow.com/questions/12135518/is-faster-than – kylefinn Sep 01 '22 at 14:12
  • @kylefinn that link addresses a different question, comparing e.g. a <= 4 to a < 5; in that situation, the branching wanted is the same, and they're just different ways of expressing the condition. In this situation, I'm talking about branching differently to avoid an extra comparison. – Tony Delroy Sep 01 '22 at 15:32
  • 1
    Oh I think I get it now. In only the case where n==lower, you get away with 1 comp instead of 2. – kylefinn Sep 01 '22 at 15:59
9

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

nsn
  • 91
  • 1
  • 1
6

Inelegant, unsafe, costly but branchless:

n= 0.5 * (n + lower + fabs(n - lower));
n= 0.5 * (n + upper - fabs(upper - n));
  • 5
    This is my favourite for sheer obtuseness; but fabs() may contain branches depending on your library. To be *REALLY* branchless you could substitute `fabs(x)` with `(x * (1 + (x < 0) * -2)` – rvalue Jun 12 '14 at 03:48
  • 7
    That assumes that the evaluation of `x < 0` is really done branchless. What about extracting the sign bit (msb) from the floating-point representation, cleanly documenting the non-portability? :-) –  Jun 12 '14 at 08:25
5

You might like the ternary operator:

value = value<lower?lower:value;
value = value>upper?upper:value;
Rich
  • 12,068
  • 9
  • 62
  • 94
1
n = n + ((n < lower) * (lower - n)) + ((n > upper) * (upper - n));
user997112
  • 29,025
  • 43
  • 182
  • 361
1

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 }

Achyut Sarma
  • 119
  • 7
0

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 */
Woody
  • 145
  • 1
  • 4