Since C11 you can use the new _Generic
selection feature
#define GET_MIN(VALUE) _Generic((VALUE), \
char : CHAR_MIN, \
signed char : SCHAR_MIN, \
short : SHRT_MIN, \
int : INT_MIN, \
long : LONG_MIN, \
long long : LLONG_MIN, \
default : 0 /* unsigned types */)
#define GET_MAX(VALUE) _Generic((VALUE), \
char : CHAR_MAX, \
unsigned char : UCHAR_MAX, \
signed char : SCHAR_MAX, \
short : SHRT_MAX, \
unsigned short : USHRT_MAX, \
int : INT_MAX, \
unsigned int : UINT_MAX, \
long : LONG_MAX, \
unsigned long : ULONG_MAX, \
long long : LLONG_MAX, \
unsigned long long : ULLONG_MAX)
#define CLAMP(TO, X) ((X) < GET_MIN((TO)(X)) \
? GET_MIN((TO)(X)) \
: ((X) > GET_MAX((TO)(X)) ? GET_MAX((TO)(X)) : (TO)(X)))
You can remove the unnecessary types to make it shorter. After that just call it as CLAMP(type, value)
like this
int main(void)
{
printf("%d\n", CLAMP(char, 1234));
printf("%d\n", CLAMP(char, -1234));
printf("%d\n", CLAMP(int8_t, 12));
printf("%d\n", CLAMP(int8_t, -34));
printf("%d\n", CLAMP(unsigned char, 1234));
printf("%d\n", CLAMP(unsigned char, -1234));
printf("%d\n", CLAMP(uint8_t, 12));
printf("%d\n", CLAMP(uint8_t, -34));
}
This way you can clamp to almost any types, including floating-point types or _Bool
if you add more types to the support list. Beware of the type width and signness issues when using it
Demo on Godlbolt
You can also use the GNU typeof
or __auto_type
extensions to make the CLAMP
macro cleaner and safer. These extensions also work in older C versions so you can use them in you don't have access to C11
Another simple way to do this in older C versions is to specify the destination bitwidth
// Note: Won't work for (unsigned) long long and needs some additional changes
#define CLAMP_SIGN(DST_BITWIDTH, X) \
((X) < -(1LL << ((DST_BITWIDTH) - 1)) \
? -(1LL << ((DST_BITWIDTH) - 1)) \
: ((X) > ((1LL << ((DST_BITWIDTH) - 1)) - 1) \
? ((1LL << ((DST_BITWIDTH) - 1)) - 1) \
: (X)))
#define CLAMP_UNSIGN(DST_BITWIDTH, X) \
((X) < 0 ? 0 : \
((X) > ((1LL << (DST_BITWIDTH)) - 1) ? \
((1LL << (DST_BITWIDTH)) - 1) : (X)))
// DST_BITWIDTH < 0 for signed types, > 0 for unsigned types
#define CLAMP(DST_BITWIDTH, X) (DST_BITWIDTH) < 0 \
? CLAMP_SIGN(-(DST_BITWIDTH), (X)) \
: CLAMP_UNSIGN((DST_BITWIDTH), (X))
Beside the fact that it doesn't work for long long
and unsigned long long
without some changes, this also implies the use of 2's complements. You can call CLAMP
with a negative bit width to indicate a signed type or call CLAMP_SIGN
/CLAMP_UNSIGN
direction
Another disadvantage is that it just clamps the values and doesn't cast to the expected type (but you can use typeof
or __auto_type
as above to return the correct type)
Demo on Godbolt
CLAMP_SIGN(8, 300)
CLAMP_SIGN(8, -300)
CLAMP_UNSIGN(8, 1234)
CLAMP_UNSIGN(8, -1234)
CLAMP(-8, 1234)
CLAMP(-8, -1234)
CLAMP(8, 12)
CLAMP(8, -34)
#define CLAMP(v,lo,hi) (v)<(lo)?(lo):(v)>(hi)?(hi):(v)
. From here, you can define specific macros for each destination type you target. – Folkway