ideasman42
ideasman42

Reputation: 48068

Introspect type min/max with C

I was wondering if there was some way in C, to introspect the maximum of a type.

So for example I have a variable called a which is an unsigned short...

{
    unsigned short a;
    long long max = TYPEOF_MAX(a);
    /* now max will be USHRT_MAX */
}

{
    signed char a;
    long long max = TYPEOF_MAX(a);
    /* now max will be CHAR_MAX */
}

Where TYPEOF_MAX is a macro which uses some method to get the range based on in type (which is static).

Other qualifiers...

Note: This is for use with generated code, obviously including <lmits.h> and using USHRT_MAX and CHAR_MAX works fine in almost all cases.

Upvotes: 3

Views: 202

Answers (4)

ideasman42
ideasman42

Reputation: 48068

This example uses C11 generics based on @Pedro Henrique A. Oliveira's answer,

Note, it could have more types added (ssize_t size_t intptr_t... etc).

#include <stdlib.h>
#include <stdio.h>
#include <limits.h>
#include <float.h>
#include <stdint.h>

#define TYPEOF_MAX(x) \
    _Generic(x, \
        bool: 1, \
        char: CHAR_MAX, signed char: SCHAR_MAX, unsigned char: UCHAR_MAX, \
        signed short: SHRT_MAX, unsigned short: USHRT_MAX, \
        signed int: INT_MAX, unsigned int: UINT_MAX, \
        signed long: LONG_MAX, unsigned long: ULONG_MAX, \
        signed long long: LLONG_MAX, unsigned long long: ULLONG_MAX, \
        float: FLT_MAX, double: DBL_MAX)


#define TYPEOF_MIN(x) \
    _Generic(x, \
        bool: 0, \
        char: CHAR_MIN, signed char: SCHAR_MIN, unsigned char: 0, \
        signed short: SHRT_MIN, unsigned short: 0, \
        signed int: INT_MIN, unsigned int: 0, \
        signed long: LONG_MIN, unsigned long: 0, \
        signed long long: LLONG_MIN, unsigned long long: 0, \
        float: -FLT_MAX, double: -DBL_MAX)

/* change 100 to 1000 - static asserts work! */
_Static_assert(TYPEOF_MAX((char)4) > 100, "example check");


int main(void)
{
    short       num_short;
    int         num_int;
    double      num_double;
    signed char num_char;


    printf("  %ld max short\n",     TYPEOF_MAX(num_short));
    printf("  %ld max int\n",       TYPEOF_MAX(num_int));
    printf("  %f  max double\n",    TYPEOF_MAX(num_double));
    printf("  %ld max char\n",      TYPEOF_MAX(num_char));
    return 0;
}

Upvotes: 2

Ben Jackson
Ben Jackson

Reputation: 93770

If you are willing to make assumptions about number representations rather than get bogged down in standards (that support architectures you're hardly likely to ever care about), you can just stuff ~0 into a variable of your target type (for unsigned types) and then assign that value to max. For signed (or potentially signed) types you stuff in ~0 and check a < 0. If so, assume 2's complement and go from there. You've already decided that long long can hold any value of any of the types you care about, so you can stash a copy of a before smashing it (assuming this trick has to work given only a).

I just whipped up a test example to verify that this all reduces to a constant with ordinary optimization (with gcc):

a = ~0;
if (a < 0)  // true if a is signed
{
    a ^= 1 << (sizeof(a) * 8 - 1);  // flip sign bit to get all 1's positive
    max = a;
    min = -a - 1;
}
else
{
    max = a;
    min = 0;
}

Upvotes: 0

ideasman42
ideasman42

Reputation: 48068

This is a solution that uses GCC's __builtin_types_compatible_p, While it works it has the following drawbacks.

  • GCC Only (no MSVC)
  • Missing types won't fail at build time (instead at runtime).
  • Can't be used outside of a function.
  • Not considered a constant, so you can't use with static-assert for example.

Sample code:

#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <limits.h>
#include <math.h>
#include <float.h>

#define TYPEOF_MAX(x)                                                         \
  ({                                                                          \
    typeof(x) tmp;                                                            \
    if      (__builtin_types_compatible_p(typeof(x), signed char))            \
      tmp = (typeof(x))CHAR_MAX;                                              \
    else if (__builtin_types_compatible_p(typeof(x), unsigned char))          \
      tmp = (typeof(x))UCHAR_MAX;                                             \
    else if (__builtin_types_compatible_p(typeof(x), signed short))           \
      tmp = (typeof(x))SHRT_MAX;                                              \
    else if (__builtin_types_compatible_p(typeof(x), unsigned short))         \
      tmp = (typeof(x))USHRT_MAX;                                             \
    else if (__builtin_types_compatible_p(typeof(x), signed int))             \
      tmp = (typeof(x))INT_MAX;                                               \
    else if (__builtin_types_compatible_p(typeof(x), unsigned int))           \
      tmp = (typeof(x))UINT_MAX;                                              \
    else if (__builtin_types_compatible_p(typeof(x), signed long))            \
      tmp = (typeof(x))LONG_MAX;                                              \
    else if (__builtin_types_compatible_p(typeof(x), unsigned long))          \
      tmp = (typeof(x))ULONG_MAX;                                             \
    else if (__builtin_types_compatible_p(typeof(x), float))                  \
      tmp = (typeof(x))FLT_MAX;                                               \
    else if (__builtin_types_compatible_p(typeof(x), double))                 \
      tmp = (typeof(x))DBL_MAX;                                               \
    else                                                                      \
      abort ();                                                               \
    tmp;                                                                      \
  })


int main(void)
{
    short       num_short;
    int         num_int;
    double      num_double;
    signed char num_char;


    printf("  %ld max short\n",     TYPEOF_MAX(num_short));
    printf("  %ld max int\n",       TYPEOF_MAX(num_int));
    printf("  %f  max double\n",    TYPEOF_MAX(num_double));
    printf("  %ld max char\n",      TYPEOF_MAX(num_char));
    return 0;
}

Upvotes: 2

I've never did c11, however it does have a feature that could help you, according to wikipedia called "Type-generic expressions". From what I understand, you can do _Generic(a, int: INT_MAX, long: LONG_MAX, default: SOME_MANINGFUL_DEFAULT_VALUE). This will inspect the type of a and according to its type, select some expression to evaluate, which will be the result of the type-generic expression.

It's not the best solution, but all you gotta do is have a macro which uses _Generic and deals with all the arithmetic types you're interested in.

GCC 4.9 (https://gcc.gnu.org/wiki/C11Status) seems to support it.

The wikipedia page might explain it better than I did: http://en.wikipedia.org/wiki/C11_(C_standard_revision)

Upvotes: 5

Related Questions