1
votes

Consider the code below which swaps nibbles in a byte:

#include <stdio.h>

unsigned char swapNibbles(unsigned char x)
{
    return ( (x & 0x0F)<<4 | (x & 0xF0)>>4 );
}

int main()
{
    unsigned char x = 100;
    printf("%u", swapNibbles(x));
    return 0;
}

Why is anding with 0x0F and 0xF0 necessary? Instead I can just write

return ((x<<4) | (x>>4));

I have googled about it and people seem to say that it won't work with negative numbers as it would pad the number with ones in right shift however anding with 0xF0 would not make the number positive? What am I missing?

2
You miss nothing, ((x<<4) | (x>>4)) will work (as long as x is unsigned char). - HolyBlackCat
@HolyBlackCat: .... and that CHAR_BIT == 8. It is an extremely rare thing but might not true for some DSP microcontrollers and is thus needed for standard complying code - Andreas H.
AFAIK this is the case for Java, as Java bytes are always signed and sign-extended to ints. Maybe the code was ported, or written by someone who used Java. - Karsten Koop
((x<<4) | (x>>4)) will actually not give the correct result, since x is promoted to int before doing the shift. Only by converting to unsigned char in the return do we get the correct result. - Falk Hüffner
@FalkHüffner it'll work because unsigned char is always zero-extended to int, not signed-extended, hence there's no 1 in the high bytes - phuclv

2 Answers

1
votes

The two variants are 100% equivalent. Both gcc and clang are able to transform each code snippet to a simple rol instruction and compile them to identical assembly code (with optimizations enabled ofcourse)

unsigned char swapNibbles(unsigned char x)
{
    return (x & 0x0F)<<4 | (x & 0xF0)>>4;
}

unsigned char swapNibbles2(unsigned char x)
{
    return  x << 4 | x >>4 ;
}
swapNibbles(unsigned char):                       # @swapNibbles(unsigned char)
        mov     eax, edi
        rol     al, 4
        ret
swapNibbles2(unsigned char):                      # @swapNibbles2(unsigned char)
        mov     eax, edi
        rol     al, 4
        ret
0
votes

Your example is perfectly fine without anding.

If swapNibbles had signed char parameter, then one may think that anding is there to fix the expression. However, let's analyse the expression then:

  • (x&0x0f)<<4: if x is negative, then x&0x0f expression becomes a non-negative value, and the result fits into int/unsigned int (the type which x gets promoted), so it is OK to perform the shift
  • (x&0xf0)>>4: if x is negative, then x&0xf0 is negative too, so shifting is implementation-defined. For example, on x86, this will replicate the sign bit -> not what we wanted. In this case, a better expression is (x>>4)&0xf (still implementation defined, but works on x86)

So this expression has implementation defined behavior for signed char, this behavior is not what we wanted on x86.

It is better to cast signed char to unsigned char, and perform shift on the unsigned value.

Note: I've presumed that signed char is stored in two-complement, and char is 8-bit (which is implied in the question, as we talk about 4-bit nibbles here).