Skip to main content
1 of 5
Madagascar
  • 10.1k
  • 1
  • 16
  • 52

Generic Min() for Integer Types (including Boolean)

This is a follow-up to Generic Max() for Integer Types (including Boolean) and An Attempt at Creating Generic Min()/Max() for Fundamental Types.

What's new:

  • MIN(), that was abandoned despondent, has been revived. MAX() and its tests were done with in the last post, this post implements a generic MIN() with MAX().
  • Again, floating point types haven't been entertained, and types narrower than int haven't been checked for (they are implicitly promoted to int by adding 0).

Code:

#ifndef MIN_H
#define MIN_H 1

#include <limits.h>

#include "max.h"

/* Current versions of gcc and clang support -std=c2x which sets 
 * __STDC_VERSION__ to this placeholder value. GCC 14.1 does not set
 * __STDC_VERSION__ to 202311L with the std=c23 flag, but Clang 18.1 does. */
#define C23_PLACEHOLDER 202000L
    
#if defined(__STDC_VERSION__) && __STDC_VERSION >= C23_PLACEHOLDER
    /* gnu::const is stricter than unsequenced, but it doesn't matter here. */
    #define ATTRIB_CONST            [[unsequenced]]
#elif defined(__GNUC__) || defined(__clang__) || defined(__INTEL_LLVM_COMPILER)
    #define ATTRIB_CONST            __attribute__((const))
#else
    #define ATTRIB_CONST            /**/
#endif

#if defined(__GNUC__) || defined(__clang__) || defined(__INTEL_LLVM_COMPILER)
    #define ATTRIB_ALWAYS_INLINE    __attribute__((always_inline))
#else
    #define ATTRIB_ALWAYS_INLINE    /**/
#endif

#define gen_basic_min(return_type, param_type1, param_type2, suffix)   \
    ATTRIB_ALWAYS_INLINE ATTRIB_CONST static inline return_type min_##suffix(param_type1 a, param_type2 b) \
    { return a < b ? (return_type) a : (return_type) b; }

#define gen_nonbasic_min(return_type, param_type1, param_type2, cast_type, suffix) \
    ATTRIB_ALWAYS_INLINE ATTRIB_CONST static inline return_type min_##suffix(param_type1 a, param_type2 b) \
    { return max_##suffix(a, b) == a ? b : (cast_type) a; }

gen_basic_min(int, int, int, i_i)
gen_basic_min(long, int, long, i_li)
gen_basic_min(long long, int, long long, i_lli)

ATTRIB_ALWAYS_INLINE ATTRIB_CONST static inline int min_i_ui(int a, unsigned b)
{
    return max_i_ui(a, b) == b ? a : (int) b;
}

ATTRIB_ALWAYS_INLINE ATTRIB_CONST static inline int min_i_uli(int a, unsigned long b)
{
    return max_i_uli(a, b) == b ? a : (int) b;
}

ATTRIB_ALWAYS_INLINE ATTRIB_CONST static inline int min_i_ulli(int a, unsigned long long b)
{
    return max_i_ulli(a, b) == b ? a : (int) b;
}

#define MIN_I(a, b)     _Generic((b),                                   \
                            int:                    min_i_i,            \
                            unsigned int:           min_i_ui,           \
                            long int:               min_i_li,           \
                            unsigned long int:      min_i_uli,          \
                            long long int:          min_i_lli,          \
                            unsigned long long int: min_i_ulli)((a), (b))

gen_basic_min(unsigned, unsigned, unsigned, ui_ui)
gen_basic_min(unsigned, unsigned, unsigned long, ui_uli)
gen_basic_min(unsigned, unsigned, unsigned long long, ui_ulli)

ATTRIB_ALWAYS_INLINE ATTRIB_CONST static inline int min_ui_i(unsigned a, int b)
{
    return min_i_ui(b, a);
}

ATTRIB_ALWAYS_INLINE ATTRIB_CONST static inline long min_ui_li(unsigned a, long b)
{
    #if LONG_MAX >= UINT_MAX
    return max_ui_li(a, b) == b ? (long) a : b;
    #else
    return max_ui_li(a, b) == a ? b : (long) a;
    #endif
}

ATTRIB_ALWAYS_INLINE ATTRIB_CONST static inline long long min_ui_lli(unsigned a, long long b)
{
    #if LLONG_MAX >= UINT_MAX
    return max_ui_lli(a, b) == b ? (long long) a : b;
    #else
    return max_ui_lli(a, b) == a ? b : (long long) a;
    #endif
}

#define MIN_UI(a, b)     _Generic((b),                                  \
                            int:                    min_ui_i,           \
                            unsigned int:           min_ui_ui,          \
                            long int:               min_ui_li,          \
                            unsigned long int:      min_ui_uli,         \
                            long long int:          min_ui_lli,         \
                            unsigned long long int: min_ui_ulli)((a), (b))

gen_basic_min(long, long, int, li_i)
gen_basic_min(long, long, long, li_li)
gen_basic_min(long long, long, long long, li_lli)

ATTRIB_ALWAYS_INLINE ATTRIB_CONST static inline long min_li_ui(long a, unsigned b)
{
    return min_ui_li(b, a);
}

ATTRIB_ALWAYS_INLINE ATTRIB_CONST static inline long min_li_uli(long a, unsigned long b)
{
    return max_li_uli(a, b) == b ? a : (long) b;
}

ATTRIB_ALWAYS_INLINE ATTRIB_CONST static inline long min_li_ulli(long a, unsigned long long b)
{
    return max_li_ulli(a, b) == b ? a : (long) b;
}

#define MIN_LI(a, b)    _Generic((b),                                   \
                            int:                    min_li_i,           \
                            unsigned int:           min_li_ui,          \
                            long int:               min_li_li,          \
                            unsigned long int:      min_li_uli,         \
                            long long int:          min_li_lli,         \
                            unsigned long long int: min_li_ulli)((a), (b))

gen_basic_min(unsigned, unsigned long, unsigned, uli_ui)
gen_basic_min(unsigned long, unsigned long, unsigned long, uli_uli)
gen_basic_min(unsigned long, unsigned long, unsigned long long, uli_ulli)

ATTRIB_ALWAYS_INLINE ATTRIB_CONST static inline int min_uli_i(unsigned long a, int b)
{
    return min_i_uli(b, a);
}

ATTRIB_ALWAYS_INLINE ATTRIB_CONST static inline long min_uli_li(unsigned long a, long b)
{
    return min_li_uli(b, a);
}

ATTRIB_ALWAYS_INLINE ATTRIB_CONST static inline long long min_uli_lli(unsigned long a, long long b)
{
    #if LLONG_MAX >= ULONG_MAX
    return max_uli_lli(a, b) == b ? (long long) a : b;
    #else
    return max_uli_lli(a, b) == a ? b : (long long) a;
    #endif
}

#define MIN_ULI(a, b)   _Generic((b),                                   \
                            int:                    min_uli_i,          \
                            unsigned int:           min_uli_ui,         \
                            long int:               min_uli_li,         \
                            unsigned long int:      min_uli_uli,        \
                            long long int:          min_uli_lli,        \
                            unsigned long long int: min_uli_ulli)((a), (b))

gen_basic_min(long long, long long, int, lli_i)
gen_basic_min(long long, long long, long, lli_li)
gen_basic_min(long long, long long, long long, lli_lli)

ATTRIB_ALWAYS_INLINE ATTRIB_CONST static inline long long min_lli_ui(long long a, unsigned b)
{
    return min_ui_lli(b, a);
}

ATTRIB_ALWAYS_INLINE ATTRIB_CONST static inline long long min_lli_uli(long long a, unsigned long b)
{
    return min_uli_lli(b, a);
}

ATTRIB_ALWAYS_INLINE ATTRIB_CONST static inline long long min_lli_ulli(long long a, unsigned long long b)
{
    return max_lli_ulli(a, b) == b ? a : (long long) b;
}

#define MIN_LLI(a, b)   _Generic((b),                                   \
                            int:                    min_lli_i,          \
                            unsigned int:           min_lli_ui,         \
                            long int:               min_lli_li,         \
                            unsigned long int:      min_lli_uli,        \
                            long long int:          min_lli_lli,        \
                            unsigned long long int: min_lli_ulli)((a), (b))

gen_basic_min(unsigned, unsigned long long, unsigned, ulli_ui)
gen_basic_min(unsigned long, unsigned long long, unsigned long, ulli_uli)
gen_basic_min(unsigned long long, unsigned long long, unsigned long long, ulli_ulli)

ATTRIB_ALWAYS_INLINE ATTRIB_CONST static inline int min_ulli_i(unsigned long long a, int b)
{
    return min_i_ulli(b, a);
}

ATTRIB_ALWAYS_INLINE ATTRIB_CONST static inline long min_ulli_li(unsigned long long a, long b)
{
    return min_li_ulli(b, a);
}

ATTRIB_ALWAYS_INLINE ATTRIB_CONST static inline long long min_ulli_lli(unsigned long long a, long long b)
{
    return min_lli_ulli(b, a);
}

/* gen_nonbasic_min(int, unsigned long long, int, int, ulli_i) */
/* gen_nonbasic_min(long, unsigned long long, long, long, ulli_li) */
/* gen_nonbasic_min(long long, unsigned long long, long long, long, ulli_lli) */

#define MIN_ULLI(a, b)  _Generic((b),                                   \
                            int:                    min_ulli_i,         \
                            unsigned int:           min_ulli_ui,        \
                            long int:               min_ulli_li,        \
                            unsigned long int:      min_ulli_uli,       \
                            long long int:          min_ulli_lli,       \
                            unsigned long long int: min_ulli_ulli)((a), (b))

#define MIN(a, b)       _Generic((a) + 0,                                       \
                            int:                    MIN_I((a) + 0, (b) + 0),    \
                            unsigned int:           MIN_UI((a) + 0, (b) + 0),   \
                            long int:               MIN_LI((a) + 0, (b) + 0),   \
                            unsigned long int:      MIN_ULI((a) + 0, (b) + 0),  \
                            long long int:          MIN_LLI((a) + 0, (b) + 0),  \
                            unsigned long long int: MIN_ULLI((a) + 0, (b) + 0))

#undef ATTRIB_ALWAYS_INLINE
#undef ATTRIB_CONST
#undef C23_PLACEHOLDER

#endif  /* MIN_H */

And here are some tests:

#include <limits.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "min.h"

/* Current versions of gcc and clang support -std=c2x which sets 
 * __STDC_VERSION__ to this placeholder value. GCC 14.1 does not set
 * __STDC_VERSION__ to 202311L with the std=c23 flag, but Clang 18.1 does. */
#define C23_PLACEHOLDER 202000L
    
#if defined(__STDC_VERSION__) && __STDC_VERSION >= C23_PLACEHOLDER
    #define NORETURN    [[noreturn]]
#elif defined(_MSC_VER)
    #define NORETURN    __declspec(noreturn)
#elif defined(__GNUC__) || defined(__clang__) || defined(__INTEL_LLVM_COMPILER)
    #define NORETURN    __attribute__((noreturn))
#else
    #define NORETURN    _Noreturn
#endif

NORETURN static void cassert(const char cond[static 1], 
                             const char file[static 1],
                             size_t line)
{
    fflush(stdout);
    fprintf(stderr, "Assertion failed: '%s' at %s, line %zu.\n", cond, file, line);
    exit(EXIT_FAILURE);
}

#define test(cond) do { \
    if (!(cond)) { cassert(#cond, __FILE__, __LINE__); } } while (false)

/* Get the name of a type */
#define ctypename(x) _Generic((x),                                  \
                _Bool: "_Bool",                                     \
                unsigned char: "unsigned char",                     \
                char: "char",                                       \
                signed char: "signed char",                         \
                short: "short",                                     \
                unsigned short: "unsigned short",                   \
                int: "int",                                         \
                unsigned: "unsigned",                               \
                long: "long",                                       \
                unsigned long: "unsigned long",                     \
                long long: "long long",                             \
                unsigned long long: "unsigned long long",           \
                default: "other")

static const int imax = INT_MAX;
static const unsigned umax = UINT_MAX;
static const long lmax = LONG_MAX;
static const unsigned long ulmax = ULONG_MAX;
static const long long llmax = LLONG_MAX;
static const unsigned long long ullmax = ULLONG_MAX;

static const long lmin = LONG_MIN;
static const int imin = INT_MIN;
static const long long llmin = LLONG_MIN;

static void test_min_int(void) 
{
    test(MIN(3, 5) == 3 && strcmp(ctypename(MIN(3, 5)), "int") == 0);
    test(MIN(5, 3) == 3 && strcmp(ctypename(MIN(5, 3)), "int") == 0);
    test(MIN(imax, imin) == imin && strcmp(ctypename(MIN(imax, imin)), "int") == 0);
    test(MIN(imin, imax) == imin && strcmp(ctypename(MIN(imin, imax)), "int") == 0);
    test(MIN(0, imax) == 0 && strcmp(ctypename(MIN(0, imax)), "int") == 0);
    test(MIN(0, imin) == imin && strcmp(ctypename(MIN(0, imin)), "int") == 0);
}

static void test_min_unsigned_int(void) 
{
    test(MIN(3U, 5U) == 3U && strcmp(ctypename(MIN(3U, 5U)), "unsigned") == 0);
    test(MIN(5U, 3U) == 3U && strcmp(ctypename(MIN(5U, 3U)), "unsigned") == 0);
    test(MIN(umax, 0U) == 0U && strcmp(ctypename(MIN(umax, 0U)), "unsigned") == 0);
}

static void test_min_long(void) 
{
    test(MIN(3L, 5L) == 3L && strcmp(ctypename(MIN(3L, 5L)), "long") == 0);
    test(MIN(5L, 3L) == 3L && strcmp(ctypename(MIN(5L, 3L)), "long") == 0);
    test(MIN(lmax, lmin) == lmin && strcmp(ctypename(MIN(lmax, lmin)), "long") == 0);
    test(MIN(lmin, lmax) == lmin && strcmp(ctypename(MIN(lmin, lmax)), "long") == 0);
    test(MIN(0L, lmax) == 0L && strcmp(ctypename(MIN(0L, lmax)), "long") == 0);
    test(MIN(0L, lmin) == lmin && strcmp(ctypename(MIN(0L, lmin)), "long") == 0);
}

static void test_min_unsigned_long(void) 
{
    test(MIN(3UL, 5UL) == 3UL && strcmp(ctypename(MIN(3UL, 5UL)), "unsigned long") == 0);
    test(MIN(5UL, 3UL) == 3UL && strcmp(ctypename(MIN(5UL, 3UL)), "unsigned long") == 0);
    test(MIN(ulmax, 0UL) == 0UL && strcmp(ctypename(MIN(ulmax, 0UL)), "unsigned long") == 0);
    test(MIN(0UL, ulmax) == 0UL && strcmp(ctypename(MIN(0UL, ulmax)), "unsigned long") == 0);
}

static void test_min_long_long(void) 
{
    test(MIN(3LL, 5LL) == 3LL && strcmp(ctypename(MIN(3LL, 5LL)), "long long") == 0);
    test(MIN(5LL, 3LL) == 3LL && strcmp(ctypename(MIN(5LL, 3LL)), "long long") == 0);
    test(MIN(llmax, llmin) == llmin && strcmp(ctypename(MIN(llmax, llmin)), "long long") == 0);
    test(MIN(llmin, llmax) == llmin && strcmp(ctypename(MIN(llmin, llmax)), "long long") == 0);
    test(MIN(0LL, llmax) == 0LL && strcmp(ctypename(MIN(0LL, llmax)), "long long") == 0);
    test(MIN(0LL, llmin) == llmin && strcmp(ctypename(MIN(0LL, llmin)), "long long") == 0);
}

static void test_min_unsigned_long_long(void) 
{
    test(MIN(3ULL, 5ULL) == 3ULL && strcmp(ctypename(MIN(3ULL, 5ULL)), "unsigned long long") == 0);
    test(MIN(5ULL, 3ULL) == 3ULL && strcmp(ctypename(MIN(5ULL, 3ULL)), "unsigned long long") == 0);
    test(MIN(ullmax, 0ULL) == 0ULL && strcmp(ctypename(MIN(ullmax, 0ULL)), "unsigned long long") == 0);
    test(MIN(0ULL, ullmax) == 0ULL && strcmp(ctypename(MIN(0ULL, ullmax)), "unsigned long long") == 0);
}

static void test_min_mixed(void) 
{
    test(MIN(3, 5U) == 3 && strcmp(ctypename(MIN(3, 5U)), "int") == 0);
    test(MIN(5U, 3) == 3 && strcmp(ctypename(MIN(5U, 3)), "int") == 0);

    test(MIN(3L, 5) == 3L && strcmp(ctypename(MIN(3L, 5)), "long") == 0);
    test(MIN(5, 3L) == 3L && strcmp(ctypename(MIN(5, 3L)), "long") == 0);

    test(MIN(3LL, 5) == 3LL && strcmp(ctypename(MIN(3LL, 5)), "long long") == 0);
    test(MIN(5, 3LL) == 3LL && strcmp(ctypename(MIN(5, 3LL)), "long long") == 0);

    test(MIN(3ULL, 5) == 3ULL && strcmp(ctypename(MIN(3ULL, 5)), "int") == 0);
    test(MIN(5, 3ULL) == 3ULL && strcmp(ctypename(MIN(5, 3ULL)), "int") == 0);

    test(MIN(3UL, 5U) == 3UL && strcmp(ctypename(MIN(3UL, 5U)), "unsigned") == 0);
    test(MIN(5U, 3UL) == 3UL && strcmp(ctypename(MIN(5U, 3UL)), "unsigned") == 0);
}

int main(void) 
{
    test_min_int();
    test_min_unsigned_int();
    test_min_long();
    test_min_unsigned_long();
    test_min_long_long();
    test_min_unsigned_long_long();
    test_min_mixed();
    return EXIT_SUCCESS;
}

The code compiles cleanly under Clang 18.1 with a strict set of warnings (-Wconversion included), and these tests all pass.

Review Request:

Bugs, wrong comparisons, redundant comparisons, portability. Any missing types?

Madagascar
  • 10.1k
  • 1
  • 16
  • 52