9

In some C99 code, I need to check whether variable i is in the interval [0, max] where max is known to be positive. The problem is that the type of the variable is allowed to be both signed and unsigned (by changing a typedef). How does one best check that the variable is in the interval?

The straightforward approach would be:

bool is_in_interval(my_type i, my_type max) {
    assert(max > 0);
    return (i >= 0 && i <= max);
}

This will work fine when we have typedef int my_type;. But when my_type is unsigned (i.e., typedef unsigned int my_type;), i >= 0 is always true and compilers will (rightly) warn for it, which I want to avoid. (I don't want to turn off that warning since it's useful when such comparisons actually are unintended, and just ignoring compiler warnings is not a good idea.)

My current idea is to cast i to an unsigned type and just check the upper bound:

bool is_in_interval(my_type i, my_type max) {
    assert(max > 0);
    return ((size_t) i <= (size_t) max);
}

If the signed type has a two's complement representation, any negative value should be greater than max once cast to the unsigned version, right? If so, this should work. I'm, however, uncertain whether this is an advisable approach. Is it, for example, safe to assume that signed types use two's complement on all platforms? Is there some better way to do this?

Fredrik Savje
  • 565
  • 2
  • 14
  • This would not work when your signed testing value, interpreted as `unsigned`, could be greater than (unsigned)`MAX/2`. Can that be the case? (Um. Maybe I mean *less* than.) – Jongware Feb 14 '16 at 04:06
  • 2
    I pretty sure it's guaranteed that a negative value cast to a compatible unsigned type will be greater than any positive value for the original type. However, you'll get people moaning about "weird code" if you do that. – sh1 Feb 14 '16 at 04:10
  • @Jongware Sorry, I don't quite get what you mean. `MAX` is not the limit of the type (e.g., `INT_MAX` or `UINT_MAX`). Using capital letters was misleading (It's changed now). – Fredrik Savje Feb 14 '16 at 04:11
  • @sh1 Ok, thank you. Yeah, it's weird, but is there any better alternatives? I guess one also could solve it completely in the pre-processor, but then one need an additional macro which makes the code bloated and error-prone. – Fredrik Savje Feb 14 '16 at 04:14
  • Seeing a compiler warning and *understanding* why it can safely be ignored isn't the same thing as "just" ignoring it. Warnings are indicators that you should think carefully about what you are doing, not commands which must be rigidly followed. Honestly, I don't see what the problem is. – John Coleman Feb 14 '16 at 04:19
  • I'd probably just go with the unsigned cast. I would argue that it _is_ idiomatic C, but there are a lot of people who don't like idiomatic C. – sh1 Feb 14 '16 at 04:21
  • 1
    @JohnColeman I don't have any religious aversion against ignoring compiler warnings. However, I think it's a good rule of thumb that there should be none. Otherwise, one has to keep track of the files and line numbers where warnings are OK and where they're not. The risk that one misses a real problem in that case seems quite high. – Fredrik Savje Feb 14 '16 at 04:25
  • @FredrikSavje good points. – John Coleman Feb 14 '16 at 04:31
  • @sh1 Pedantically "it's guaranteed that a negative value cast to a compatible unsigned type will be greater than any positive value for the original type" is not true. With a compatible signed and unsigned type, the max value unsigned may equal the max value of the signed type. – Some arcane systems (yet C compliant) have used comparable unsigned / signed types that are the same except simple have the sign bit off for all of the unsigned type. – chux - Reinstate Monica Feb 14 '16 at 05:00
  • @chux, Yeah, I guess I can see that. DSPs that force signed saturation on arithmetic, or somesuch. – sh1 Feb 14 '16 at 05:02
  • @chux: I wish there were a standardized dialect of "mainstream" C, which could be used by programmers who aren't targeting obscure DSPs or other devices. While it's useful to have a form of C available for such devices, they shouldn't be allowed to disrupt the development of a language suitable for mainstream computers to the extent that they do. – supercat Feb 14 '16 at 07:43
  • Can you have conditional compilation based on the signdness of the type, to remove the test against zero? Look at the crazy overloading hack used by the standard library: I recall reading how that provided a *type test* through subtle use of the ternary operator and conversion rules. Or, can you write that one function in c++ in its own file, so you can use std::is_signed ? – JDługosz Feb 14 '16 at 09:03
  • 1
    A C implementation can have integer types 'wider' (informally, larger) than `size_t` and if so that cast can change the value and give you a wrong result. `uintmax_t` from `` portably avoids this (in C99 and later, but you said C99). OTOH 2sC is not an issue; converting signed integer to unsigned in C is *defined* as effectively adding or subtracting 2 up w even on a (very very rare) 1sC or S&M machine. – dave_thompson_085 Feb 14 '16 at 12:46
  • @supercat Would "a standardized dialect" disrupt the development of _future_ computers? Although I agree with you about the value of a "standardized dialect", (e.g. Java or Latin) it is C's ability to work on all sorts of platforms that keep it relevant to new and innovated machines. – chux - Reinstate Monica Feb 14 '16 at 15:42
  • @chux: I'd argue that it would have the opposite effect. At present, 99% of production C code relies upon features which are common to many platforms, but for which there is no "official" Standard. If something can be written in a way which abides by the Standard, or in a way which is clearer and on some implementations faster, and will work on 99.9999% of existing implementations, but which the Standard considers UB, would it be better for programmers to use the first way, or for the Standard to acknowledge the behavior which 99.9999% of implementations already support? – supercat Feb 14 '16 at 17:34
  • @supercat Perhaps post your good idea on programmers.stackexchange.com for a better place than SO to review the issues? – chux - Reinstate Monica Feb 14 '16 at 17:47

5 Answers5

8

Check against 0 and max. Yet avoid a direct >= 0 test.

Casting is a problem as it may narrow the value in some implementation defined way.

bool is_in_interval(my_type i, my_type max) {
  // first, take care of the easy case
  if (i > max) return false;
  if (i > 0) return true;
  return i == 0;
}

I will ponder this some more too.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • Thank you! Could you elaborate on "Casting is a problem as it may narrow the value in some implementation defined way"? Don't quite understand how you mean. – Fredrik Savje Feb 14 '16 at 04:32
  • 1
    @Fredrik Savje Casting a `long long` (maybe the type of `my_type`) to `unsigned` is well defined, yet likely loses some range and `((size_t) i <= (size_t) max)` is then meaningless as the cast may have changed `max` value but not `i`. – chux - Reinstate Monica Feb 14 '16 at 04:37
  • Of course, I was wrongly thinking that no integer type can be larger than `size_t`. Thanks for clarifying! – Fredrik Savje Feb 14 '16 at 04:45
  • Variation on a theme: `return (i>0) ? (i <= max) : (i==0);` – chux - Reinstate Monica Feb 14 '16 at 04:56
  • @FredrikSavje you would want `uintmax_t` rather than `size_t` for that. – sh1 Feb 14 '16 at 04:59
  • 1
    @sh1 2 problems with that approach: A casted negative `i` may or may not exceed the casted `max` - ti be `>`, that assumes a certain integer layout that, though common, is not specified by C.. Systems do support, in a weaselly sort of way, integer types that exceed `uintmax_t`. (e.g. 128 bit types without calling them "integer") Although I suspect your idea will work 99.999% of the time. – chux - Reinstate Monica Feb 14 '16 at 05:06
  • @chux So, the have-the-cake-and-eat-it-too-question that follows is whether the compiler will optimize away the redundant checking when the type is unsigned? – Fredrik Savje Feb 14 '16 at 05:29
  • @Fredrik Savje That is true. Yet `if (i > 0) return true; return i == 0;` does introduce a sequence point between rather than say `return i > 0 || i == 0;` and that _I think_ prevents the "optimize away". – chux - Reinstate Monica Feb 14 '16 at 15:08
  • 1
    @Fredrik Savje I see 4 approaches: preprocessor, hiding >= in code (as above), _Generic(), 2nd level function `is_in_interval2(i, 0, max)`. Preprocessor does not understand `my_type` as it only uses `intmax_t`. Hiding >= with a sequent point. (Pedantic, `volatile my_type j = i; if (j > 0) return true; return j == 0;`. `_Generic()` would work well except it is difficult to enumerate all signed/unsigned types in a portable fashion. – chux - Reinstate Monica Feb 14 '16 at 15:18
  • 1
    @chux, That problem exists not just for nonstandard `int` types, but also for `float` and `double`. I think that for `UINTMAX_MAX` to be less than twice `INTMAX_MAX` requires that `uintmax_t` have a junk bit. I admit it's possible for any type, but most plausible for 16 bit types (DSPs). – sh1 Feb 14 '16 at 17:11
3

I think this should shut the compiler up:

bool is_in_interval(my_type i, my_type max) {
  my_type zero = 0;
  assert(max > 0);
  return (zero <= i && i <= max);
}
sh1
  • 4,324
  • 17
  • 30
  • It will shut some compilers up, but not all. Compilers vary in whether testing against a declared `const` variable will trigger warnings like "condition always true" or "condition always false". – Peter Feb 14 '16 at 05:01
  • @Peter I see, thanks! Then it became less attractive. – Fredrik Savje Feb 14 '16 at 05:06
  • I could suggest `volatile` instead of `const`, but it might be better to find the specific compiler's inline annotation to disable the warning. – sh1 Feb 14 '16 at 05:08
  • Perhaps `volatile signed char zero = 0;`? – chux - Reinstate Monica Feb 14 '16 at 15:21
  • @chux, I've used `volatile` in situations like this before, but specifically to convince the debugger to de-optimise the code so that I could edit the value in the debugger. So I know that in at least some compilers it would imply a performance penalty. – sh1 Feb 14 '16 at 17:05
  • A small advantage of `volatile signed char zero = 0` over `volatile my_type` is that `my_type` may already be `volatile` and some compliers may whine about a `volatile volatile`. – chux - Reinstate Monica Feb 14 '16 at 17:40
  • Hm. I never considered the possibility that a type could be volatile. Usually when I've tried to put properties on types like that it's with extensions that aren't implemented properly and I find they're silently dropped on the floor and I just get buggy code. – sh1 Feb 14 '16 at 18:34
2

Maybe this is another straightforward approach:

bool is_in_interval(my_type i, my_type max) {
    assert(max > 0);
    if ((my_type)-1 > 0)
        return (i <= max); // my_type is unsigned
    else
        return (0 <= i && i <= max); // my_type is signed
}
nalzok
  • 14,965
  • 21
  • 72
  • 139
  • Will not `if ((my_type)-1 > 0)` create a warning as it is always true or false. Much like OP's " always true and compilers will (rightly) warn"? – chux - Reinstate Monica Feb 14 '16 at 04:43
  • Well, clang does give me an warning, but it's on `return (0 <= i && i <= max); // my_type is signed`, saying `comparison of 0 <= unsigned expression is always true`. – nalzok Feb 14 '16 at 05:10
2

If your compiler supports it, I believe the cleanest solution would be to keep the code as is, but to use a #pragma directive to locally disable the spurious warning. For example, for GCC 4.6.4+, the following would do the trick:

bool is_in_interval(my_type i, my_type max) {
    assert(max > 0);
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wtype-limits"
    /* i >= 0 could warn if my_type is unsigned */
    return (i >= 0 && i <= max);
#pragma GCC diagnostic pop
}

Apparently, the exact same #pragmas should work for Clang too, since it imitates GCC's syntax. Other compilers may have similar syntax (e.g. #pragma warning( disable: ... ) for Visual C/C++), and it should be possible to use preprocessor directives to select the appropriate #pragmas for each compiler.

The main downside of this method is aesthetic — it litters the code with ugly #pragmas. That said, I would consider that preferable to deliberately obfuscating (and possibly de-optimizing) your code just to avoid a compiler warning. At least, with the #pragmas, it's clear to the next programmer that reads the code why it's written the way it is.


To make this cleaner and more portable, you can use the C99 _Pragma() operator to define macros that disable and re-enable the warnings, e.g. like this:

#if __GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__ >= 40604
#  define NO_TYPE_LIMIT_WARNINGS \
       _Pragma("GCC diagnostic push") \
       _Pragma("GCC diagnostic ignored \"-Wtype-limits\"")
#  define RESTORE_WARNINGS \
       _Pragma("GCC diagnostic pop")
/* Add checks for other compilers here! */
#else
#  define NO_TYPE_LIMIT_WARNINGS /* nothing */
#  define RESTORE_WARNINGS /* nothing */
#endif

and use them like this:

bool is_in_interval(my_type i, my_type max) {
    assert(max > 0);
    NO_TYPE_LIMIT_WARNINGS; /* i >= 0 could warn if my_type is unsigned */
    return (i >= 0 && i <= max);
    RESTORE_WARNINGS;
}

Alternatively, if you wanted to support pre-C99 compilers, you could create a pair of header files (without any include guards!) named something like warnings_off.h and warnings_restore.h that contain the appropriate #pragma for each compiler, and then bracket the code you want to silence warnings for with:

#include "warnings_off.h"
/* ...code that emits spurious warnings here... */
#include "warnings_restore.h"
Community
  • 1
  • 1
Ilmari Karonen
  • 49,047
  • 9
  • 93
  • 153
  • I like this idea yet see a large down side of portability: There are lots of non-gcc C compiler's (and programmers), including maybe one of OP's, that would need to re-write/`#ifdef` code. – chux - Reinstate Monica Feb 14 '16 at 15:34
  • Thank you! In some sense, this probably is the "correct" way to do it. All solutions presented so far are basically ways to trick the compiler not to warn for something it should warn for -- the obvious solution would be to just temporary disable the warning. The problem here is that I'm writing a small C library so I cannot rely on a specific compiler. – Fredrik Savje Feb 14 '16 at 20:15
2

A simple solution is to do a 2-sided test.

The only attribute really lost is modest efficiency/performance. No other problems.

Although OP has certainly considered this, any solution should be weighed against this as a reference.

bool is_in_interval2(my_type i, my_type min, my_type max) {
  return i >= min && i <= max;
}

// Could make this a macro or inline
bool is_in_interval(my_type i, my_type max) {
  return is_in_interval2(i, 0, max);
}
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256