Search code examples
ccharintnegative-number

What is the best way to get integer's negative sign and store it as char?


How to get an integer's sign and store it in a char? One way is:

int n = -5
char c;
if(n<0)
    c = '-';
else
    c = '+';

Or:

char c = n < 0 ? '-' : '+';

But is there a way to do it without conditionals?


Solution

  • There's the most efficient and portable way, but it doesn't win any beauty awards.

    We can assume that the MSB of a signed integer is always set if it is negative. This is a 100% portable assumption even when taking exotic signedness formats in account (one's complement, signed magnitude). Therefore the fastest way is to simply mask out the MSB from the integer.

    The MSB of any integer is found at location CHAR_BIT * sizeof(n) - 1;. On a typical 32 bit mainstream system, this would for example be 8 * 4 - 1 = 31.

    So we can write a function like this:

    _Bool is_signed (int n)
    {
      const unsigned int sign_bit_n = CHAR_BIT * sizeof(n) - 1;
      return (_Bool) ((unsigned int)n >> sign_bit_n);
    }
    

    On x86-64 gcc 9.1 (-O3), this results in very efficient code:

    is_signed:
            mov     eax, edi
            shr     eax, 31
            ret
    

    The advantage of this method is also that, unlike code such as x < 0, it won't risk getting translated into "branch if negative" instructions when ported.

    Complete example:

    #include <limits.h>
    #include <stdio.h>
    
    _Bool is_signed (int n)
    {
      const unsigned int sign_bit_n = CHAR_BIT * sizeof(n) - 1;
      return (_Bool) ((unsigned int)n >> sign_bit_n);
    }
    
    int main (void)
    {
      int n = -1;
    
      const char SIGNS[] = {' ', '-'};
      char sign = SIGNS[is_signed(n)];
      putchar(sign);
    }
    

    Disassembly (x86-64 gcc 9.1 (-O3)):

    is_signed:
            mov     eax, edi
            shr     eax, 31
            ret
    main:
            sub     rsp, 8
            mov     rsi, QWORD PTR stdout[rip]
            mov     edi, 45
            call    _IO_putc
            xor     eax, eax
            add     rsp, 8
            ret