Safe, fully standards-following integer abs in C

Here's a bit tricky one, because AFAIK there is no existing platform, where conditionally compiled part of the code would actually get included by compiler, so it is entirely up to the human review to see if it is correct.

The purpose is to have abs function, which does not trigger undefined behavior with any user input, on any C99 standard compliant compiler, on any platform, and in the theoretical case of result not fitting in the return type, return maximum value that fits.

Apart from any bugs, blunders etc, the real challenge is to come up with a (maybe theoretical) platform where the code doesn't produce desired result, or (hopefully) be reasonably certain that it works in all cases allowed by the standard.

// function to review
#include <inttypes.h>

uintmax_t maxed_abs(intmax_t i) {
if (i < 0) {
#if UINTMAX_MAX == INTMAX_MAX && INTMAX_MIN < -INTMAX_MAX
// catch the theoretical case of 2's complement platform,
// where unsigned and signed have same number of value bits
if (i == INTMAX_MIN) return UINTMAX_MAX;
#endif
return -(uintmax_t)i;
}
return (uintmax_t)i;
}

// test code
#include <stdio.h>

int main(void)
{
printf("Enter int in range %jd .. %jd:\n > ", INTMAX_MIN, INTMAX_MAX);
intmax_t i;
if (scanf("%jd", &i) == 1) {
printf("Result: |%jd| = %ju\n", i, maxed_abs(i));
}
}

• Taking the negative of an unsigned type like in -(uintmax_t)i invokes undefined behaviour though. Do you mean (uintmax_t) -i? – David Foerster Feb 7 '16 at 22:08
• @DavidFoerster I don't think taking negative of unsigned invokes UB. All operations (except dovision by zero) are well defined for unsigned types. – hyde Feb 8 '16 at 7:17
• Looks like you're right: stackoverflow.com/questions/8026694/…. I still believe that (uintmax_t) -i is more readable. – David Foerster Feb 8 '16 at 9:05
• @DavidFoerster -i might cause signed integer overflow with value MAXINT_MIN. – hyde Feb 8 '16 at 15:41

1 Answer

1. This kind of code relies on careful reading of the standard, so if it were me I would add cross-references to the sections of the standard that I was relying on, for example I might write:

/* This relies on the following facts from the C99 standard:
*
* §6.2.5.9 The range of nonnegative values of a signed integer
* type is a subrange of the corresponding unsigned integer type.
* It follows from this and §7.18.2.5 that UINTMAX_MAX >=
* INTMAX_MAX.
*
* §6.2.6.2.2 Signed integers are represented as sign-and-
* magnitude, two's complement, or one's complement. It follows
* that INTMAX_MIN is either -INTMAX_MAX (in sign-and-magnitude or
* one's complement) or -INTMAX_MAX-1 (in two's complement).
*/

2. I don't like writing code that can't be tested on any platform that I have access to. Although the code looks good in this case, if you pursued this approach on a bigger scale, then it would be very surprising if all the untested code were correct. I would prefer to write something like:

#if UINTMAX_MAX == INTMAX_MAX && INTMAX_MIN < -INTMAX_MAX
#error "Two's complement with UINTMAX_MAX == INTMAX_MAX is not supported."
#endif


so that untested platforms get a compiler error instead of compiling and executing untested code. In the unlikely event that you discover such a platform, you can add the code and test it at that point.