# Signed Integers are Asymmetrical

What’s wrong with this code?

``````int8_t absolute(int8_t x) {
if (x >= 0) {
return x;
} else {
return -x;
}
}
``````

Seems straightforward enough. Let’s try it with some representative numbers:

``````#include <stdint.h>
#include <stdio.h>

int8_t absolute(int8_t x) {
if (x >= 0) {
return x;
} else {
return -x;
}
}

int main() {
int8_t values[5] = {INT8_MIN, INT8_MIN + 1, 0, INT8_MAX - 1, INT8_MAX};
for (int i = 0; i < 5; i++) {
int8_t x = values[i];
printf("abs(%4i) = %4i\n", x, absolute(x));
}
return 0;
}
``````

Running this code yields:

``````eudoxia@bullroarer \$ gcc cabs.c
eudoxia@bullroarer \$ ./a.out
abs(-128) = -128
abs(-127) =  127
abs(   0) =    0
abs( 126) =  126
abs( 127) =  127
``````

The very first case is wrong. Why? Because signed integers are asymmetrical around zero. Note how `INT8_MAX` is 127, while `INT8_MIN` is -128. You can think of it in terms of a number line, with the negative side being larger by one:

More generally: if a signed two’s complement number has n bits, the largest number it can represent is 2(n - 1) - 1, while the most negative number it can represent is -2(n - 1)

You can think of unary negation as rotating a number around zero on the number line. Evaluating `-(-127)` rotates the number and lands on 127:

Evaluating `-(-128)` rotates the number around zero, but it lands one step beyond `INT8_MAX`. Because of overflow, it lands right back on `INT8_MIN`.

Note that compiling with `-ftrapv` doesn’t help. Neither GCC nor Clang catch this. Ada does, though:

``````with Ada.Text_IO;

type Signed_Byte is new Integer range -128 .. 127;

function Absolute(X: Signed_Byte) return Signed_Byte is
begin
if X >= 0 then
return X;
else
return -X;
end if;
end Absolute;

type Index is range 1 .. 5;
type Value_Array is array (Index) of Signed_Byte;

Values: Value_Array := (127, 126, 0, -127, -128);

package Signed_Byte_IO is new Ada.Text_IO.Integer_IO (Signed_Byte);
begin
for I in Index loop
declare
X: Signed_Byte := Values(I);
begin
Signed_Byte_IO.Put(X);
Signed_Byte_IO.Put(Absolute(X));
end;
end loop;
``````

Running this yields:

``````eudoxia@bullroarer \$ gnatmake adaabs.adb
abs( 127) =  127
abs( 126) =  126
abs(   0) =    0
abs(-127) =  127
abs(-128) =

``````

I only became aware of this issue from an AdaCore case study, probably this, about using SPARK Ada to prove overflow-safety in an implementation of the absolute value function. Here is such an implementation.

This case was a big part of my motivation for having pervasive overflow checking in Austral: the fact that the trivial implementation of the absolute value function – which matches its mathematical definition exactly – is subtly wrong should be humbling, and prove the futility of having programmers mentally track every overflow possibility.

The takeaway: when working with fixed-width integers, test on extremal values. And don’t trust `-ftrapv`.