Search code examples
cintegerunsigned-integer

Emulating signed integers using unsigned integers in C


In Jens Gustedt's book Modern C, on page 59, he explains how signed integers can be emulated using unsigned integers. His example code shows how one can implement a comparison of two unsigned integers reinterpreted as signed integers:

bool is_negative(unsigned a) { 
   unsigned const int_max = UINT_MAX /2;
   return a > int_max;
}

bool is_signed_less(unsigned a, unsigned b) {
   if (is_negative(b) && !is_negative(a)) return false;
   else return a < b; 
} 

Do I misunderstand something here or does he miss the second special case where is_negative(a) = true and is_negative(b) = false?

For example if we want to have a = -1 and b = 1, then, using two's complement, we would represent them as

unsigned int a = UINT_MAX; 
unsigned int b = 1;    

(e.g. for a 4 bit integer we would have a = 1111 and b = 0001). Now we have is_negative(a) returns true, and is_negative(b) returns false. When calling is_signed_less(a, b) we end up in the else clause and a < b (now interpreted as unsigned integers) will return false. However, it is clearly true that -1 < 1, so the function returns the wrong result.

Is this a typo in the code of the book or is there something that I do not understand?


Solution

  • This is what happens when people try to be "smart" instead of following "keep it simple, stupid" best practices. Good engineering involves writing the code as simple as you possibly can, for example:

    bool is_signed_less_correct (unsigned a, unsigned b) {
      bool is_neg_a = is_negative(a);
      bool is_neg_b = is_negative(b);
    
      if(is_neg_a != is_neg_b) // one is negative
      {
        return is_neg_a; // if one is negative and it is a, return true otherwise false
      } 
    
      // both are negative or both are positive
      return a < b;
    }
    

    Even this code a bit too "smart" still, since it implicitly uses the fact that -1 == 0xFFFF... is the largest 2's complement signed number and therefore a < b holds true no matter if these are negative or not, as long as they are both of the same signedness.

    Then of course you would always write a little unit test to sanity check it: https://godbolt.org/z/h4nKsffqr

    Output:

    -2 < -1 ? true  (is_signed_less_gustedt)
    -1 < -1 ? false (is_signed_less_gustedt)
    -1 <  0 ? false (is_signed_less_gustedt)
     0 < -1 ? false (is_signed_less_gustedt)
     0 <  1 ? true  (is_signed_less_gustedt)
     1 <  0 ? false (is_signed_less_gustedt)
     1 <  1 ? false (is_signed_less_gustedt)
    
    -2 < -1 ? true  (is_signed_less_correct)
    -1 < -1 ? false (is_signed_less_correct)
    -1 <  0 ? true  (is_signed_less_correct)
     0 < -1 ? false (is_signed_less_correct)
     0 <  1 ? true  (is_signed_less_correct)
     1 <  0 ? false (is_signed_less_correct)
     1 <  1 ? false (is_signed_less_correct)