C: finding the maximum and minimum of the type of an arithmetic expression
Asked Answered
U

2

6

I need to find the maximum and minimum of an arbitrary C expression which has no side effects. The following macros work on my machine. Will they work on all platforms? If not, can they be modified to work? My intent is to subsequently use these to implement macros like SAFE_MUL(a,b) in place of a*b. SAFE_MUL would include a check for multiplication overflow.

EDIT: the type is cast as suggested by Steve.

#include <stdio.h>
#include <limits.h>

#define IS_SIGNED(exp) (((exp)*0-1) < 0)

#define TYPE_MAX_UNSIGNED(exp) ((exp)*0-1)

#define TYPE_MAX_SIGNED(exp) ( \
    sizeof (exp) == sizeof (int) \
    ? \
    INT_MAX \
    : \
    ( \
        sizeof (exp) == sizeof (long) \
        ? \
        LONG_MAX \
        : \
        LLONG_MAX \
    ) \
)

#define TYPE_MAX(exp) ((unsigned long long)( \
    IS_SIGNED (exp) \
    ? \
    TYPE_MAX_SIGNED (exp) \
    : \
    TYPE_MAX_UNSIGNED (exp) \
))

#define TYPE_MIN_SIGNED(exp) ( \
    sizeof (exp) == sizeof (int) \
    ? \
    INT_MIN \
    : \
    ( \
        sizeof (exp) == sizeof (long) \
        ? \
        LONG_MIN \
        : \
        LLONG_MIN \
    ) \
)

#define TYPE_MIN(exp) ((long long)( \
    IS_SIGNED (exp) \
    ? \
    TYPE_MIN_SIGNED (exp) \
    : \
    (exp)*0 \
))

int
main (void) {

    printf ("TYPE_MAX (1 + 1) = %lld\n", TYPE_MAX (1 + 1));
    printf ("TYPE_MAX (1 + 1L) = %lld\n", TYPE_MAX (1 + 1L));
    printf ("TYPE_MAX (1 + 1LL) = %lld\n", TYPE_MAX (1 + 1LL));
    printf ("TYPE_MAX (1 + 1U) = %llu\n", TYPE_MAX (1 + 1U));
    printf ("TYPE_MAX (1 + 1UL) = %llu\n", TYPE_MAX (1 + 1UL));
    printf ("TYPE_MAX (1 + 1ULL) = %llu\n", TYPE_MAX (1 + 1ULL));
    printf ("TYPE_MIN (1 + 1) = %lld\n", TYPE_MIN (1 + 1));
    printf ("TYPE_MIN (1 + 1L) = %lld\n", TYPE_MIN (1 + 1L));
    printf ("TYPE_MIN (1 + 1LL) = %lld\n", TYPE_MIN (1 + 1LL));
    printf ("TYPE_MIN (1 + 1U) = %llu\n", TYPE_MIN (1 + 1U));
    printf ("TYPE_MIN (1 + 1UL) = %llu\n", TYPE_MIN (1 + 1UL));
    printf ("TYPE_MIN (1 + 1ULL) = %llu\n", TYPE_MIN (1 + 1ULL));
    return 0;
}
Underbid answered 31/10, 2011 at 14:44 Comment(6)
Great question! One thing however, I would replace the multiplication by zero with an XOR with itself; it's an optimization that many embedded compilers will not perform.Salamis
@mahmoud: I am not sure I understood the comment. I would like all the macros in the example to be evaluated at compile time. So an XOR that results in these macro translating to anything more than constants is actually undesirable from my point of view. In other words, the purpose of (exp)*0 is to get a 0 cast to the type of (exp) subject to the constraint that the type of exp is not smaller than an intUnderbid
@user1016492 Your question seems to be more general than the example because you talk about arbitrary C expression which has no side effects, which is not necessarily a compilation-time constant; also SAFE_MUL(a,b) seems to be much more useful if a and b aren't constants.Laryngology
@user1016492 I understand. Using exp^exp in its stead will accomplish the same, I think.Salamis
@Laryngology as an example, lets say there is an expression exp1: (x+1) * y * z / (x + y), where x, y, z may themselves be arbitrary expressions. I expected an optimising compiler to be able to evaluate TYPE_MAX (exp1) at compile time, regardless of the value of x, y and z. Having said that, I agree that the question is more general than what the example can handle, for 3 reasons (1 pointed out by steve): (1) exp consisting of unsigned constants smaller than int wont work. (2) exp involving float, double wont work. (3) The value returned by TYPE_MAX may be lesser than the actualUnderbid
@anatolyg: I omitted to add that you pointed out (2) in your answer belowUnderbid
E
3
  • The IS_SIGNED macro doesn't tell the truth with unsigned types smaller than int. IS_SIGNED((unsigned char)1) is true on any normal implementation, because the type of (unsigned char)1*0 is int, not unsigned char.

Your eventual SAFE macros should still tell the truth about whether overflow occurs, since the same integer promotions apply to all arithmetic. But they'll tell you whether overflow occurs in the multiplication, not necessarily whether it occurs when the user converts the result back to the original type of one of the operands.

Come to think of it, though, you probably knew that already since your macros don't attempt to suggest CHAR_MIN and so on. But other people finding this question in future might not realise that restriction.

  • There is no single type guaranteed to be able to hold all the values that TYPE_MIN and TYPE_MAX can evaluate to. But you could make TYPE_MAX always evaluate to unsigned long long (and the value always fits in that type), and the same with TYPE_MIN and signed long long. This would allow you to use a correct printf format without using your knowledge of whether the expression is signed. Currently TYPE_MAX(1) is a long long, whereas TYPE_MAX(1ULL) is an unsigned long long.

  • Technically it's permitted for int and long to have the same size but different ranges, due to long having fewer padding bits than int. I doubt that any important implementation does that, though.

Es answered 31/10, 2011 at 14:49 Comment(6)
I really wonder why C (and C++) has been defined so loosely. It makes developing so much harder.Wallack
@steve: thanks. The issue of padding bits is a concern because there is no way to tell for sure from sizeof (exp) whether it is an int or long. So one can only depend on the promotion rules to get the more restrictive range which suffices for my purpose.Underbid
@nightcracker Because on the flip side, it makes implementing it so much easier; especially on embedded devices and platforms.Salamis
@nightcracker: yes I agree. Portable code is such a pain to write and so easy to get wrong. I wonder if any open source code is truely portable to the word of the standard.Underbid
@steve: is there any guarantee that any types other than int e.g. (short, char, long, long long) do NOT have padding bitsUnderbid
@user1016492: Only the char types (char, signed char, unsigned char) are guaranteed to have no padding bits, plus all the fixed-width types (u)intN_t are guaranteed no padding bits if they exist.Es
L
1

Just an idea: if you use gcc, you can use its typeof extension:

#define IS_SIGNED(exp) ((typeof(exp))-1 < 0)
#define TYPE_MAX_UNSIGNED(exp) ((typeof(exp))-1)
#define TYPE_MAX_SIGNED(exp) ... // i cannot improve your code here

Edit: might also check for floating-point types:

#define CHECK_INT(exp) ((typeof(exp))1 / 2 == 0)
#define CHECK_INT(exp) (((exp) * 0 + 1) / 2 == 0) // if cannot use typeof
#define MY_CONST_1(exp) (1/CHECK_INT(exp))
// Now replace any 1 in code by MY_CONST_1(exp) to cause error for floating-point
Laryngology answered 31/10, 2011 at 15:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.